diff options
-rw-r--r-- | pjmedia/include/pjmedia/sdp.h | 11 | ||||
-rw-r--r-- | pjmedia/include/pjmedia/transport.h | 88 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/endpoint.c | 28 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/sdp.c | 34 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/transport_ice.c | 1174 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/transport_srtp.c | 468 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/transport_udp.c | 52 | ||||
-rw-r--r-- | pjsip/include/pjsua-lib/pjsua_internal.h | 23 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_call.c | 119 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_media.c | 67 |
10 files changed, 1466 insertions, 598 deletions
diff --git a/pjmedia/include/pjmedia/sdp.h b/pjmedia/include/pjmedia/sdp.h index 768af9f1..73c4393d 100644 --- a/pjmedia/include/pjmedia/sdp.h +++ b/pjmedia/include/pjmedia/sdp.h @@ -318,6 +318,17 @@ PJ_DECL(pj_status_t) pjmedia_sdp_attr_get_rtcp(const pjmedia_sdp_attr *attr, pjmedia_sdp_rtcp_attr *rtcp); +/** + * Create a=rtcp attribute. + * + * @param pool Pool to create the attribute. + * @param a Socket address. + * + * @return SDP RTCP attribute. + */ +PJ_DECL(pjmedia_sdp_attr*) pjmedia_sdp_attr_create_rtcp(pj_pool_t *pool, + const pj_sockaddr *a); + /* ************************************************************************** * SDP CONNECTION INFO diff --git a/pjmedia/include/pjmedia/transport.h b/pjmedia/include/pjmedia/transport.h index b898f65b..99043492 100644 --- a/pjmedia/include/pjmedia/transport.h +++ b/pjmedia/include/pjmedia/transport.h @@ -320,20 +320,31 @@ struct pjmedia_transport_op pj_size_t size); /** - * This function is called by application to generate the SDP parts - * related to transport type, e.g: ICE, SRTP. + * Prepare the transport for a new media session. * * Application should call #pjmedia_transport_media_create() instead of * calling this function directly. */ pj_status_t (*media_create)(pjmedia_transport *tp, - pj_pool_t *pool, + pj_pool_t *sdp_pool, unsigned options, - pjmedia_sdp_session *sdp_local, - const pjmedia_sdp_session *sdp_remote, + const pjmedia_sdp_session *remote_sdp, unsigned media_index); /** + * This function is called by application to generate the SDP parts + * related to transport type, e.g: ICE, SRTP. + * + * Application should call #pjmedia_transport_encode_sdp() instead of + * calling this function directly. + */ + pj_status_t (*encode_sdp)(pjmedia_transport *tp, + pj_pool_t *sdp_pool, + pjmedia_sdp_session *sdp_local, + const pjmedia_sdp_session *rem_sdp, + unsigned media_index); + + /** * This function is called by application to start the transport * based on SDP negotiation result. * @@ -341,8 +352,8 @@ struct pjmedia_transport_op * calling this function directly. */ pj_status_t (*media_start) (pjmedia_transport *tp, - pj_pool_t *pool, - pjmedia_sdp_session *sdp_local, + pj_pool_t *tmp_pool, + const pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *sdp_remote, unsigned media_index); @@ -626,38 +637,62 @@ PJ_INLINE(pj_status_t) pjmedia_transport_send_rtcp2(pjmedia_transport *tp, /** - * Generate local SDP parts that are related to the specified media transport. - * Remote SDP might be needed as reference when application is in deciding - * side of negotiation (callee side), otherwise it should be NULL. - * - * This API is provided to allow the media transport to add more information - * in the SDP offer, before the offer is sent to remote. Additionally, for - * answerer side, this callback allows the media transport to reject the - * offer before this offer is processed by the SDP negotiator. + * Prepare the media transport for a new media session, and optionally + * encode the relevant information in the \a sdp_local. Application must + * call this function before starting a new media session using this + * transport. * * This is just a simple wrapper which calls <tt>media_create()</tt> member * of the transport. * * @param tp The media transport. - * @param pool The memory pool. + * @param sdp_pool Pool object to allocate memory related to SDP + * messaging components. * @param option Option flags, from #pjmedia_tranport_media_option - * @param sdp_local Local SDP. - * @param sdp_remote Remote SDP. + * @param rem_sdp Remote SDP if it's available. * @param media_index Media index in SDP. * * @return PJ_SUCCESS on success, or the appropriate error code. */ PJ_INLINE(pj_status_t) pjmedia_transport_media_create(pjmedia_transport *tp, - pj_pool_t *pool, + pj_pool_t *sdp_pool, unsigned options, - pjmedia_sdp_session *sdp_local, - const pjmedia_sdp_session *sdp_remote, + const pjmedia_sdp_session *rem_sdp, unsigned media_index) { - return (*tp->op->media_create)(tp, pool, options, sdp_local, sdp_remote, + return (*tp->op->media_create)(tp, sdp_pool, options, rem_sdp, media_index); } + +/** + * Put transport specific information into the SDP. This function can be + * called to create SDP offer or answer, depending whether \a rem_sdp + * parameter is present. + * + * This is just a simple wrapper which calls <tt>encode_sdp()</tt> member + * of the transport. + * + * @param tp The media transport. + * @param sdp_pool Pool object to allocate memory related to SDP + * messaging components. + * @param sdp The local SDP to be filled in information from the + * media transport. + * @param rem_sdp Remote SDP if it's available. + * @param media_index Media index in SDP. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_INLINE(pj_status_t) pjmedia_transport_encode_sdp(pjmedia_transport *tp, + pj_pool_t *sdp_pool, + pjmedia_sdp_session *sdp, + const pjmedia_sdp_session *rem_sdp, + unsigned media_index) +{ + return (*tp->op->encode_sdp)(tp, sdp_pool, sdp, rem_sdp, media_index); +} + + /** * Start the transport with regards to SDP negotiation result. * This API should be called after offer and answer are negotiated, and @@ -673,7 +708,7 @@ PJ_INLINE(pj_status_t) pjmedia_transport_media_create(pjmedia_transport *tp, * of the transport. * * @param tp The media transport. - * @param pool The memory pool. + * @param tmp_pool The memory pool for allocating temporary objects. * @param option The media transport option. * @param sdp_local Local SDP. * @param sdp_remote Remote SDP. @@ -682,12 +717,13 @@ PJ_INLINE(pj_status_t) pjmedia_transport_media_create(pjmedia_transport *tp, * @return PJ_SUCCESS on success, or the appropriate error code. */ PJ_INLINE(pj_status_t) pjmedia_transport_media_start(pjmedia_transport *tp, - pj_pool_t *pool, - pjmedia_sdp_session *sdp_local, + pj_pool_t *tmp_pool, + const pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *sdp_remote, unsigned media_index) { - return (*tp->op->media_start)(tp, pool, sdp_local, sdp_remote, media_index); + return (*tp->op->media_start)(tp, tmp_pool, sdp_local, sdp_remote, + media_index); } diff --git a/pjmedia/src/pjmedia/endpoint.c b/pjmedia/src/pjmedia/endpoint.c index 2835aef4..2492afcd 100644 --- a/pjmedia/src/pjmedia/endpoint.c +++ b/pjmedia/src/pjmedia/endpoint.c @@ -362,31 +362,9 @@ PJ_DEF(pj_status_t) pjmedia_endpt_create_sdp( pjmedia_endpt *endpt, /* Add "rtcp" attribute */ #if defined(PJMEDIA_HAS_RTCP_IN_SDP) && PJMEDIA_HAS_RTCP_IN_SDP!=0 if (sock_info->rtcp_addr_name.addr.sa_family != 0) { - const pj_sockaddr *rtcp_addr = &sock_info->rtcp_addr_name; - - attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr); - attr->name = pj_str("rtcp"); - attr->value.ptr = (char*) pj_pool_alloc(pool, 80); - if (rtcp_addr->addr.sa_family == pj_AF_INET()) { - attr->value.slen = - pj_ansi_snprintf(attr->value.ptr, 80, - "%u IN IP4 %s", - pj_ntohs(rtcp_addr->ipv4.sin_port), - pj_inet_ntoa(rtcp_addr->ipv4.sin_addr)); - } else if (rtcp_addr->addr.sa_family == pj_AF_INET6()) { - char tmp_addr[PJ_INET6_ADDRSTRLEN]; - attr->value.slen = - pj_ansi_snprintf(attr->value.ptr, 80, - "%u IN IP6 %s", - pj_sockaddr_get_port(rtcp_addr), - pj_sockaddr_print(rtcp_addr, tmp_addr, - sizeof(tmp_addr), 0)); - - } else { - pj_assert(!"Unsupported address family"); - return PJ_EAFNOTSUP; - } - pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); + attr = pjmedia_sdp_attr_create_rtcp(pool, &sock_info->rtcp_addr_name); + if (attr) + pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); } #endif diff --git a/pjmedia/src/pjmedia/sdp.c b/pjmedia/src/pjmedia/sdp.c index 91c4ce1e..a1e13c7c 100644 --- a/pjmedia/src/pjmedia/sdp.c +++ b/pjmedia/src/pjmedia/sdp.c @@ -414,6 +414,40 @@ PJ_DEF(pj_status_t) pjmedia_sdp_attr_get_rtcp(const pjmedia_sdp_attr *attr, } +PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_attr_create_rtcp(pj_pool_t *pool, + const pj_sockaddr *a) +{ + enum { + ATTR_LEN = PJ_INET6_ADDRSTRLEN+16 + }; + pjmedia_sdp_attr *attr; + + attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr); + attr->name = pj_str("rtcp"); + attr->value.ptr = (char*) pj_pool_alloc(pool, ATTR_LEN); + if (a->addr.sa_family == pj_AF_INET()) { + attr->value.slen = + pj_ansi_snprintf(attr->value.ptr, ATTR_LEN, + "%u IN IP4 %s", + pj_ntohs(a->ipv4.sin_port), + pj_inet_ntoa(a->ipv4.sin_addr)); + } else if (a->addr.sa_family == pj_AF_INET6()) { + char tmp_addr[PJ_INET6_ADDRSTRLEN]; + attr->value.slen = + pj_ansi_snprintf(attr->value.ptr, ATTR_LEN, + "%u IN IP6 %s", + pj_sockaddr_get_port(a), + pj_sockaddr_print(a, tmp_addr, + sizeof(tmp_addr), 0)); + + } else { + pj_assert(!"Unsupported address family"); + return NULL; + } + + return attr; +} + PJ_DEF(pj_status_t) pjmedia_sdp_attr_to_rtpmap(pj_pool_t *pool, const pjmedia_sdp_attr *attr, diff --git a/pjmedia/src/pjmedia/transport_ice.c b/pjmedia/src/pjmedia/transport_ice.c index facc8ed1..95301036 100644 --- a/pjmedia/src/pjmedia/transport_ice.c +++ b/pjmedia/src/pjmedia/transport_ice.c @@ -23,19 +23,43 @@ #include <pj/rand.h> #define THIS_FILE "transport_ice.c" +#if 0 +# define TRACE__(expr) PJ_LOG(5,expr) +#else +# define TRACE__(expr) +#endif -static const pj_str_t ID_RTP_AVP = { "RTP/AVP", 7 }; +enum oa_role +{ + ROLE_NONE, + ROLE_OFFERER, + ROLE_ANSWERER +}; + +struct sdp_state +{ + unsigned match_comp_cnt; /* Matching number of components */ + pj_bool_t ice_mismatch; /* Address doesn't match candidates */ + pj_bool_t ice_restart; /* Offer to restart ICE */ + pj_ice_sess_role local_role; /* Our role */ +}; struct transport_ice { pjmedia_transport base; pj_pool_t *pool; int af; + unsigned comp_cnt; pj_ice_strans *ice_st; + pjmedia_ice_cb cb; unsigned media_option; + pj_bool_t initial_sdp; + enum oa_role oa_role; /**< Last role in SDP offer/answer */ + struct sdp_state rem_offer_state;/**< Describes the remote offer */ + void *stream; pj_sockaddr_in remote_rtp; pj_sockaddr_in remote_rtcp; @@ -84,12 +108,16 @@ static pj_status_t transport_send_rtcp2(pjmedia_transport *tp, static pj_status_t transport_media_create(pjmedia_transport *tp, pj_pool_t *pool, unsigned options, - pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *rem_sdp, unsigned media_index); -static pj_status_t transport_media_start (pjmedia_transport *tp, +static pj_status_t transport_encode_sdp(pjmedia_transport *tp, + pj_pool_t *tmp_pool, + pjmedia_sdp_session *sdp_local, + const pjmedia_sdp_session *rem_sdp, + unsigned media_index); +static pj_status_t transport_media_start(pjmedia_transport *tp, pj_pool_t *pool, - pjmedia_sdp_session *sdp_local, + const pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *rem_sdp, unsigned media_index); static pj_status_t transport_media_stop(pjmedia_transport *tp); @@ -120,16 +148,28 @@ static pjmedia_transport_op transport_ice_op = &transport_send_rtcp, &transport_send_rtcp2, &transport_media_create, + &transport_encode_sdp, &transport_media_start, &transport_media_stop, &transport_simulate_lost, &transport_destroy }; -static const pj_str_t STR_CANDIDATE = {"candidate", 9}; -static const pj_str_t STR_ICE_LITE = {"ice-lite", 8}; -static const pj_str_t STR_ICE_MISMATCH = {"ice-mismatch", 12}; - +static const pj_str_t STR_RTP_AVP = { "RTP/AVP", 7 }; +static const pj_str_t STR_CANDIDATE = { "candidate", 9}; +static const pj_str_t STR_REM_CAND = { "remote-candidates", 17 }; +static const pj_str_t STR_ICE_LITE = { "ice-lite", 8}; +static const pj_str_t STR_ICE_MISMATCH = { "ice-mismatch", 12}; +static const pj_str_t STR_ICE_UFRAG = { "ice-ufrag", 9 }; +static const pj_str_t STR_ICE_PWD = { "ice-pwd", 7 }; +static const pj_str_t STR_IP4 = { "IP4", 3 }; +static const pj_str_t STR_IP6 = { "IP6", 3 }; +static const pj_str_t STR_RTCP = { "rtcp", 4 }; + +enum { + COMP_RTP = 1, + COMP_RTCP = 2 +}; /* * Create ICE media transport. @@ -157,6 +197,8 @@ PJ_DEF(pj_status_t) pjmedia_ice_create(pjmedia_endpt *endpt, pj_ansi_strcpy(tp_ice->base.name, pool->obj_name); tp_ice->base.op = &transport_ice_op; tp_ice->base.type = PJMEDIA_TRANSPORT_TYPE_ICE; + tp_ice->initial_sdp = PJ_TRUE; + tp_ice->oa_role = ROLE_NONE; if (cb) pj_memcpy(&tp_ice->cb, cb, sizeof(pjmedia_ice_cb)); @@ -184,6 +226,24 @@ PJ_DEF(pj_status_t) pjmedia_ice_create(pjmedia_endpt *endpt, return PJ_SUCCESS; } +/* Disable ICE when SDP from remote doesn't contain a=candidate line */ +static void set_no_ice(struct transport_ice *tp_ice, const char *reason, + pj_status_t err) +{ + if (err != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(err, errmsg, sizeof(errmsg)); + PJ_LOG(4,(tp_ice->base.name, + "Stopping ICE, reason=%s:%s", reason, errmsg)); + } else { + PJ_LOG(4,(tp_ice->base.name, + "Stopping ICE, reason=%s", reason)); + } + + pj_ice_strans_stop_ice(tp_ice->ice_st); +} + + /* Create SDP candidate attribute */ static int print_sdp_cand_attr(char *buffer, int max_len, const pj_ice_sess_cand *cand) @@ -227,102 +287,259 @@ static int print_sdp_cand_attr(char *buffer, int max_len, return len+len2; } -/* - * For both UAC and UAS, pass in the SDP before sending it to remote. - * This will add ICE attributes to the SDP. - */ -static pj_status_t transport_media_create(pjmedia_transport *tp, - pj_pool_t *pool, - unsigned options, - pjmedia_sdp_session *sdp_local, - const pjmedia_sdp_session *rem_sdp, - unsigned media_index) -{ - struct transport_ice *tp_ice = (struct transport_ice*)tp; - pj_bool_t init_ice; - unsigned i; - pj_status_t status; - - tp_ice->media_option = options; - /* Validate media transport */ - /* For now, this transport only support RTP/AVP transport */ - if ((tp_ice->media_option & PJMEDIA_TPMED_NO_TRANSPORT_CHECKING) == 0) { - pjmedia_sdp_media *m_rem, *m_loc; +/* Get ice-ufrag and ice-pwd attribute */ +static void get_ice_attr(const pjmedia_sdp_session *rem_sdp, + const pjmedia_sdp_media *rem_m, + const pjmedia_sdp_attr **p_ice_ufrag, + const pjmedia_sdp_attr **p_ice_pwd) +{ + pjmedia_sdp_attr *attr; - m_rem = rem_sdp? rem_sdp->media[media_index] : NULL; - m_loc = sdp_local->media[media_index]; + /* Find ice-ufrag attribute in media descriptor */ + attr = pjmedia_sdp_attr_find(rem_m->attr_count, rem_m->attr, + &STR_ICE_UFRAG, NULL); + if (attr == NULL) { + /* Find ice-ufrag attribute in session descriptor */ + attr = pjmedia_sdp_attr_find(rem_sdp->attr_count, rem_sdp->attr, + &STR_ICE_UFRAG, NULL); + } + *p_ice_ufrag = attr; - if (pj_stricmp(&m_loc->desc.transport, &ID_RTP_AVP) || - (m_rem && pj_stricmp(&m_rem->desc.transport, &ID_RTP_AVP))) - { - pjmedia_sdp_media_deactivate(pool, m_loc); - return PJMEDIA_SDP_EINPROTO; - } + /* Find ice-pwd attribute in media descriptor */ + attr = pjmedia_sdp_attr_find(rem_m->attr_count, rem_m->attr, + &STR_ICE_PWD, NULL); + if (attr == NULL) { + /* Find ice-pwd attribute in session descriptor */ + attr = pjmedia_sdp_attr_find(rem_sdp->attr_count, rem_sdp->attr, + &STR_ICE_PWD, NULL); } + *p_ice_pwd = attr; +} - /* If we are UAS, check that the incoming SDP contains support for ICE. */ - if (rem_sdp) { - const pjmedia_sdp_media *rem_m; - rem_m = rem_sdp->media[media_index]; +/* Encode and add "a=ice-mismatch" attribute in the SDP */ +static void encode_ice_mismatch(pj_pool_t *sdp_pool, + pjmedia_sdp_session *sdp_local, + unsigned media_index) +{ + pjmedia_sdp_attr *attr; + pjmedia_sdp_media *m = sdp_local->media[media_index]; - init_ice = pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr, - "ice-ufrag", NULL) != NULL; - if (init_ice == PJ_FALSE) { - init_ice = pjmedia_sdp_attr_find2(rem_sdp->attr_count, - rem_sdp->attr, - "ice-ufrag", NULL) != NULL; - } + attr = PJ_POOL_ALLOC_T(sdp_pool, pjmedia_sdp_attr); + attr->name = STR_ICE_MISMATCH; + attr->value.slen = 0; + pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); +} - if (init_ice) { - init_ice = pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr, - "candidate", NULL) != NULL; - } - } else { - init_ice = PJ_TRUE; - } - /* Init ICE */ - if (init_ice) { - pj_ice_sess_role ice_role; - enum { MAXLEN = 256 }; - pj_str_t ufrag, pass; - char *buffer; - pjmedia_sdp_attr *attr; +/* Encode ICE information in SDP */ +static pj_status_t encode_session_in_sdp(struct transport_ice *tp_ice, + pj_pool_t *sdp_pool, + pjmedia_sdp_session *sdp_local, + unsigned media_index, + unsigned comp_cnt, + pj_bool_t restart_session) +{ + enum { + ATTR_BUF_LEN = 160, /* Max len of a=candidate attr */ + RATTR_BUF_LEN= 160 /* Max len of a=remote-candidates attr */ + }; + pjmedia_sdp_media *m = sdp_local->media[media_index]; + pj_str_t local_ufrag, local_pwd; + pjmedia_sdp_attr *attr; + pj_status_t status; + + /* Must have a session */ + PJ_ASSERT_RETURN(pj_ice_strans_has_sess(tp_ice->ice_st), PJ_EBUG); + + /* Get ufrag and pwd from current session */ + pj_ice_strans_get_ufrag_pwd(tp_ice->ice_st, &local_ufrag, &local_pwd, + NULL, NULL); + + /* The listing of candidates depends on whether ICE has completed + * or not. When ICE has completed: + * + * 9.1.2.2: Existing Media Streams with ICE Completed + * The agent MUST include a candidate attributes for candidates + * matching the default destination for each component of the + * media stream, and MUST NOT include any other candidates. + * + * When ICE has not completed, we shall include all candidates. + * + * Except when we have detected that remote is offering to restart + * the session, in this case we will answer with full ICE SDP and + * new ufrag/pwd pair. + */ + if (!restart_session && pj_ice_strans_sess_is_complete(tp_ice->ice_st)) { + const pj_ice_sess_check *check; + char *attr_buf; + pjmedia_sdp_conn *conn; + pjmedia_sdp_attr *a_rtcp; + pj_str_t rem_cand; unsigned comp; - ice_role = (rem_sdp==NULL ? PJ_ICE_SESS_ROLE_CONTROLLING : - PJ_ICE_SESS_ROLE_CONTROLLED); + /* Encode ice-ufrag attribute */ + attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_UFRAG.ptr, + &local_ufrag); + pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); + + /* Encode ice-pwd attribute */ + attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_PWD.ptr, + &local_pwd); + pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); + + /* Prepare buffer */ + attr_buf = (char*) pj_pool_alloc(sdp_pool, ATTR_BUF_LEN); + rem_cand.ptr = (char*) pj_pool_alloc(sdp_pool, RATTR_BUF_LEN); + rem_cand.slen = 0; + + /* 9.1.2.2: Existing Media Streams with ICE Completed + * The default destination for media (i.e., the values of + * the IP addresses and ports in the m and c line used for + * that media stream) MUST be the local candidate from the + * highest priority nominated pair in the valid list for each + * component. + */ + check = pj_ice_strans_get_valid_pair(tp_ice->ice_st, 1); + if (check == NULL) { + pj_assert(!"Shouldn't happen"); + return PJ_EBUG; + } - ufrag.ptr = (char*) pj_pool_alloc(pool, PJ_ICE_UFRAG_LEN); - pj_create_random_string(ufrag.ptr, PJ_ICE_UFRAG_LEN); - ufrag.slen = PJ_ICE_UFRAG_LEN; + /* Override connection line address and media port number */ + conn = m->conn; + if (conn == NULL) + conn = sdp_local->conn; + + conn->addr.ptr = (char*) pj_pool_alloc(sdp_pool, + PJ_INET6_ADDRSTRLEN); + pj_sockaddr_print(&check->lcand->addr, conn->addr.ptr, + PJ_INET6_ADDRSTRLEN, 0); + conn->addr.slen = pj_ansi_strlen(conn->addr.ptr); + m->desc.port = pj_sockaddr_get_port(&check->lcand->addr); + + /* Override address RTCP attribute if it's present */ + if (comp_cnt == 2 && + (check = pj_ice_strans_get_valid_pair(tp_ice->ice_st, + COMP_RTCP)) != NULL && + (a_rtcp = pjmedia_sdp_attr_find(m->attr_count, m->attr, + &STR_RTCP, 0)) != NULL) + { + pjmedia_sdp_attr_remove(&m->attr_count, m->attr, a_rtcp); - pass.ptr = (char*) pj_pool_alloc(pool, PJ_ICE_UFRAG_LEN); - pj_create_random_string(pass.ptr, PJ_ICE_UFRAG_LEN); - pass.slen = PJ_ICE_UFRAG_LEN; + a_rtcp = pjmedia_sdp_attr_create_rtcp(sdp_pool, + &check->lcand->addr); + if (a_rtcp) + pjmedia_sdp_attr_add(&m->attr_count, m->attr, a_rtcp); + } - status = pj_ice_strans_init_ice(tp_ice->ice_st, ice_role, - &ufrag, &pass); - if (status != PJ_SUCCESS) - return status; + /* Encode only candidates matching the default destination + * for each component + */ + for (comp=0; comp < comp_cnt; ++comp) { + int len; + pj_str_t value; + + /* Get valid pair for this component */ + check = pj_ice_strans_get_valid_pair(tp_ice->ice_st, comp+1); + if (check == NULL) + continue; + + /* Print and add local candidate in the pair */ + value.ptr = attr_buf; + value.slen = print_sdp_cand_attr(attr_buf, ATTR_BUF_LEN, + check->lcand); + if (value.slen < 0) { + pj_assert(!"Not enough attr_buf to print candidate"); + return PJ_EBUG; + } - /* Create ice-ufrag attribute */ - attr = pjmedia_sdp_attr_create(pool, "ice-ufrag", &ufrag); - sdp_local->attr[sdp_local->attr_count++] = attr; + attr = pjmedia_sdp_attr_create(sdp_pool, STR_CANDIDATE.ptr, + &value); + pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); + + /* Append to a=remote-candidates attribute */ + if (pj_ice_strans_get_role(tp_ice->ice_st) == + PJ_ICE_SESS_ROLE_CONTROLLING) + { + char rem_addr[PJ_INET6_ADDRSTRLEN]; + + pj_sockaddr_print(&check->rcand->addr, rem_addr, + sizeof(rem_addr), 0); + len = pj_ansi_snprintf( + rem_cand.ptr + rem_cand.slen, + RATTR_BUF_LEN - rem_cand.slen, + "%s%u %s %u", + (rem_cand.slen==0? "" : " "), + comp+1, rem_addr, + pj_sockaddr_get_port(&check->rcand->addr) + ); + if (len < 1 || len >= RATTR_BUF_LEN) { + pj_assert(!"Not enough buffer to print " + "remote-candidates"); + return PJ_EBUG; + } + + rem_cand.slen += len; + } + } - /* Create ice-pwd attribute */ - attr = pjmedia_sdp_attr_create(pool, "ice-pwd", &pass); - sdp_local->attr[sdp_local->attr_count++] = attr; + /* 9.1.2.2: Existing Media Streams with ICE Completed + * In addition, if the agent is controlling, it MUST include + * the a=remote-candidates attribute for each media stream + * whose check list is in the Completed state. The attribute + * contains the remote candidates from the highest priority + * nominated pair in the valid list for each component of that + * media stream. + */ + if (pj_ice_strans_get_role(tp_ice->ice_st) == + PJ_ICE_SESS_ROLE_CONTROLLING) + { + attr = pjmedia_sdp_attr_create(sdp_pool, STR_REM_CAND.ptr, + &rem_cand); + pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); + } + } else if (pj_ice_strans_has_sess(tp_ice->ice_st)) { /* Encode all candidates to SDP media */ + char *attr_buf; + unsigned comp; + + /* If ICE is not restarted, encode current ICE ufrag/pwd. + * Otherwise generate new one. + */ + if (!restart_session) { + attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_UFRAG.ptr, + &local_ufrag); + pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); + + attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_PWD.ptr, + &local_pwd); + pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); + + } else { + pj_str_t str; + + str.slen = PJ_ICE_UFRAG_LEN; + str.ptr = (char*) pj_pool_alloc(sdp_pool, str.slen); + pj_create_random_string(str.ptr, str.slen); + attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_UFRAG.ptr, &str); + pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); + + str.ptr = (char*) pj_pool_alloc(sdp_pool, str.slen); + pj_create_random_string(str.ptr, str.slen); + attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_PWD.ptr, &str); + pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); + } - buffer = (char*) pj_pool_alloc(pool, MAXLEN); + /* Create buffer to encode candidates as SDP attribute */ + attr_buf = (char*) pj_pool_alloc(sdp_pool, ATTR_BUF_LEN); - for (comp=0; comp < tp_ice->comp_cnt; ++comp) { + for (comp=0; comp < comp_cnt; ++comp) { unsigned cand_cnt; pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND]; + unsigned i; cand_cnt = PJ_ARRAY_SIZE(cand); status = pj_ice_strans_enum_cands(tp_ice->ice_st, comp+1, @@ -331,45 +548,51 @@ static pj_status_t transport_media_create(pjmedia_transport *tp, return status; for (i=0; i<cand_cnt; ++i) { - pjmedia_sdp_media *m; pj_str_t value; - value.slen = print_sdp_cand_attr(buffer, MAXLEN, &cand[i]); + value.slen = print_sdp_cand_attr(attr_buf, ATTR_BUF_LEN, + &cand[i]); if (value.slen < 0) { - pj_assert(!"Not enough buffer to print candidate"); + pj_assert(!"Not enough attr_buf to print candidate"); return PJ_EBUG; } - value.ptr = buffer; - attr = pjmedia_sdp_attr_create(pool, "candidate", &value); - m = sdp_local->media[media_index]; - m->attr[m->attr_count++] = attr; + value.ptr = attr_buf; + attr = pjmedia_sdp_attr_create(sdp_pool, + STR_CANDIDATE.ptr, + &value); + pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); } } + } else { + /* ICE has failed, application should have terminated this call */ } - /* Done */ return PJ_SUCCESS; } /* Parse a=candidate line */ -static pj_status_t parse_cand(pj_pool_t *pool, +static pj_status_t parse_cand(const char *obj_name, + pj_pool_t *pool, const pj_str_t *orig_input, pj_ice_sess_cand *cand) { pj_str_t input; char *token, *host; + int af; pj_str_t s; pj_status_t status = PJNATH_EICEINCANDSDP; pj_bzero(cand, sizeof(*cand)); pj_strdup_with_null(pool, &input, orig_input); + PJ_UNUSED_ARG(obj_name); + /* Foundation */ token = strtok(input.ptr, " "); if (!token) { - PJ_LOG(5,(THIS_FILE, "Expecting ICE foundation in candidate")); + TRACE__((obj_name, "Expecting ICE foundation in candidate")); goto on_return; } pj_strdup2(pool, &cand->foundation, token); @@ -377,7 +600,7 @@ static pj_status_t parse_cand(pj_pool_t *pool, /* Component ID */ token = strtok(NULL, " "); if (!token) { - PJ_LOG(5,(THIS_FILE, "Expecting ICE component ID in candidate")); + TRACE__((obj_name, "Expecting ICE component ID in candidate")); goto on_return; } cand->comp_id = (pj_uint8_t) atoi(token); @@ -385,19 +608,19 @@ static pj_status_t parse_cand(pj_pool_t *pool, /* Transport */ token = strtok(NULL, " "); if (!token) { - PJ_LOG(5,(THIS_FILE, "Expecting ICE transport in candidate")); + TRACE__((obj_name, "Expecting ICE transport in candidate")); goto on_return; } if (pj_ansi_stricmp(token, "UDP") != 0) { - PJ_LOG(5,(THIS_FILE, - "Expecting ICE UDP transport only in candidate")); + TRACE__((obj_name, + "Expecting ICE UDP transport only in candidate")); goto on_return; } /* Priority */ token = strtok(NULL, " "); if (!token) { - PJ_LOG(5,(THIS_FILE, "Expecting ICE priority in candidate")); + TRACE__((obj_name, "Expecting ICE priority in candidate")); goto on_return; } cand->prio = atoi(token); @@ -405,38 +628,43 @@ static pj_status_t parse_cand(pj_pool_t *pool, /* Host */ host = strtok(NULL, " "); if (!host) { - PJ_LOG(5,(THIS_FILE, "Expecting ICE host in candidate")); + TRACE__((obj_name, "Expecting ICE host in candidate")); goto on_return; } - if (pj_sockaddr_in_init(&cand->addr.ipv4, pj_cstr(&s, host), 0)) { - PJ_LOG(5,(THIS_FILE, - "Expecting ICE IPv4 transport address in candidate")); + /* Detect address family */ + if (pj_ansi_strchr(host, ':')) + af = pj_AF_INET6(); + else + af = pj_AF_INET(); + /* Assign address */ + if (pj_sockaddr_init(af, &cand->addr, pj_cstr(&s, host), 0)) { + TRACE__((obj_name, "Invalid ICE candidate address")); goto on_return; } /* Port */ token = strtok(NULL, " "); if (!token) { - PJ_LOG(5,(THIS_FILE, "Expecting ICE port number in candidate")); + TRACE__((obj_name, "Expecting ICE port number in candidate")); goto on_return; } - cand->addr.ipv4.sin_port = pj_htons((pj_uint16_t)atoi(token)); + pj_sockaddr_set_port(&cand->addr, (pj_uint16_t)atoi(token)); /* typ */ token = strtok(NULL, " "); if (!token) { - PJ_LOG(5,(THIS_FILE, "Expecting ICE \"typ\" in candidate")); + TRACE__((obj_name, "Expecting ICE \"typ\" in candidate")); goto on_return; } if (pj_ansi_stricmp(token, "typ") != 0) { - PJ_LOG(5,(THIS_FILE, "Expecting ICE \"typ\" in candidate")); + TRACE__((obj_name, "Expecting ICE \"typ\" in candidate")); goto on_return; } /* candidate type */ token = strtok(NULL, " "); if (!token) { - PJ_LOG(5,(THIS_FILE, "Expecting ICE candidate type in candidate")); + TRACE__((obj_name, "Expecting ICE candidate type in candidate")); goto on_return; } @@ -453,12 +681,11 @@ static pj_status_t parse_cand(pj_pool_t *pool, cand->type = PJ_ICE_CAND_TYPE_PRFLX; } else { - PJ_LOG(5,(THIS_FILE, "Invalid ICE candidate type %s in candidate", + PJ_LOG(5,(obj_name, "Invalid ICE candidate type %s in candidate", token)); goto on_return; } - status = PJ_SUCCESS; on_return: @@ -466,182 +693,681 @@ on_return: } -/* Disable ICE when SDP from remote doesn't contain a=candidate line */ -static void set_no_ice(struct transport_ice *tp_ice, const char *reason) +/* Create initial SDP offer */ +static pj_status_t create_initial_offer(struct transport_ice *tp_ice, + pj_pool_t *sdp_pool, + pjmedia_sdp_session *loc_sdp, + unsigned media_index) +{ + pj_status_t status; + + /* Encode ICE in SDP */ + status = encode_session_in_sdp(tp_ice, sdp_pool, loc_sdp, media_index, + tp_ice->comp_cnt, PJ_FALSE); + if (status != PJ_SUCCESS) { + set_no_ice(tp_ice, "Error encoding SDP answer", status); + return status; + } + + return PJ_SUCCESS; +} + + +/* Verify incoming offer */ +static pj_status_t verify_ice_sdp(struct transport_ice *tp_ice, + pj_pool_t *tmp_pool, + const pjmedia_sdp_session *rem_sdp, + unsigned media_index, + pj_ice_sess_role current_ice_role, + struct sdp_state *sdp_state) { + const pjmedia_sdp_media *rem_m; + const pjmedia_sdp_attr *attr, *ufrag_attr, *pwd_attr; + const pjmedia_sdp_conn *rem_conn; + pj_bool_t comp1_found=PJ_FALSE, comp2_found=PJ_FALSE; + pj_sockaddr rem_conn_addr, rtcp_addr; + unsigned i; + pj_status_t status; + + rem_m = rem_sdp->media[media_index]; + + /* Get the "ice-ufrag" and "ice-pwd" attributes */ + get_ice_attr(rem_sdp, rem_m, &ufrag_attr, &pwd_attr); + + /* If "ice-ufrag" or "ice-pwd" are not found, disable ICE */ + if (ufrag_attr==NULL || pwd_attr==NULL) { + sdp_state->match_comp_cnt = 0; + return PJ_SUCCESS; + } + + /* Verify that default target for each component matches one of the + * candidatefor the component. Otherwise stop ICE with ICE ice_mismatch + * error. + */ + + /* Component 1 is the c= line */ + rem_conn = rem_m->conn; + if (rem_conn == NULL) + rem_conn = rem_sdp->conn; + if (!rem_conn) + return PJMEDIA_SDP_EMISSINGCONN; + + /* Verify address family matches */ + if ((tp_ice->af==pj_AF_INET() && + pj_strcmp(&rem_conn->addr_type, &STR_IP4)!=0) || + (tp_ice->af==pj_AF_INET6() && + pj_strcmp(&rem_conn->addr_type, &STR_IP6)!=0)) + { + return PJMEDIA_SDP_ETPORTNOTEQUAL; + } + + /* Assign remote connection address */ + status = pj_sockaddr_init(tp_ice->af, &rem_conn_addr, &rem_conn->addr, + (pj_uint16_t)rem_m->desc.port); + if (status != PJ_SUCCESS) + return status; + + /* Component 2 is a=rtcp line, if present. */ + attr = pjmedia_sdp_attr_find(rem_m->attr_count, rem_m->attr, + &STR_RTCP, NULL); + if (attr && tp_ice->comp_cnt > 1) { + pjmedia_sdp_rtcp_attr rtcp_attr; + + status = pjmedia_sdp_attr_get_rtcp(attr, &rtcp_attr); + if (status != PJ_SUCCESS) { + /* Error parsing a=rtcp attribute */ + return status; + } + + /* Verify address family matches */ + if ((tp_ice->af==pj_AF_INET() && + pj_strcmp(&rtcp_attr.addr_type, &STR_IP4)!=0) || + (tp_ice->af==pj_AF_INET6() && + pj_strcmp(&rtcp_attr.addr_type, &STR_IP6)!=0)) + { + return PJMEDIA_SDP_ETPORTNOTEQUAL; + } + + /* Assign RTCP address */ + status = pj_sockaddr_init(tp_ice->af, &rtcp_addr, + &rtcp_attr.addr, + (pj_uint16_t)rtcp_attr.port); + if (status != PJ_SUCCESS) { + return PJMEDIA_SDP_EINRTCP; + } + + sdp_state->match_comp_cnt = 2; + + } else { + /* Don't have RTCP component */ + comp2_found = PJ_TRUE; + sdp_state->match_comp_cnt = 1; + } + + /* Find the default address in a=candidate attributes. + */ + for (i=0; i<rem_m->attr_count; ++i) { + pj_ice_sess_cand cand; + + if (pj_strcmp(&rem_m->attr[i]->name, &STR_CANDIDATE)!=0) + continue; + + status = parse_cand(tp_ice->base.name, tmp_pool, + &rem_m->attr[i]->value, &cand); + if (status != PJ_SUCCESS) { + PJ_LOG(4,(tp_ice->base.name, + "Error in parsing SDP candidate attribute '%.*s', " + "candidate is ignored", + (int)rem_m->attr[i]->value.slen, + rem_m->attr[i]->value.ptr)); + continue; + } + + if (!comp1_found && cand.comp_id==COMP_RTP && + pj_sockaddr_cmp(&rem_conn_addr, &cand.addr)==0) + { + /* Found */ + comp1_found = PJ_TRUE; + if (comp1_found && comp2_found) + break; + } else if (!comp2_found && cand.comp_id==COMP_RTCP && + pj_sockaddr_cmp(&rtcp_addr, &cand.addr)==0) + { + /* Found */ + comp2_found = PJ_TRUE; + if (comp1_found && comp2_found) + break; + } + + } + + if (!comp1_found || !comp2_found) { + /* ICE ice_mismatch */ + sdp_state->ice_mismatch = PJ_TRUE; + } else { + sdp_state->ice_mismatch = PJ_FALSE; + } + + /* Detect remote restarting session */ + if (pj_ice_strans_has_sess(tp_ice->ice_st) && + (pj_ice_strans_sess_is_running(tp_ice->ice_st) || + pj_ice_strans_sess_is_complete(tp_ice->ice_st))) + { + pj_str_t rem_run_ufrag, rem_run_pwd; + pj_ice_strans_get_ufrag_pwd(tp_ice->ice_st, NULL, NULL, + &rem_run_ufrag, &rem_run_pwd); + if (pj_strcmp(&ufrag_attr->value, &rem_run_ufrag) || + pj_strcmp(&pwd_attr->value, &rem_run_pwd)) + { + /* Remote offers to restart ICE */ + sdp_state->ice_restart = PJ_TRUE; + } else { + sdp_state->ice_restart = PJ_FALSE; + } + } else { + sdp_state->ice_restart = PJ_FALSE; + } + + /* Detect our role */ + if (current_ice_role==PJ_ICE_SESS_ROLE_CONTROLLING) { + sdp_state->local_role = PJ_ICE_SESS_ROLE_CONTROLLING; + } else { + if (pjmedia_sdp_attr_find(rem_sdp->attr_count, rem_sdp->attr, + &STR_ICE_LITE, NULL) != NULL) + { + /* Remote is ICE Lite */ + sdp_state->local_role = PJ_ICE_SESS_ROLE_CONTROLLING; + } else { + sdp_state->local_role = PJ_ICE_SESS_ROLE_CONTROLLED; + } + } + PJ_LOG(4,(tp_ice->base.name, - "Disabling local ICE, reason=%s", reason)); - transport_media_stop(&tp_ice->base); + "Processing SDP: support ICE=%u, common comp_cnt=%u, " + "ice_mismatch=%u, ice_restart=%u, local_role=%s", + (sdp_state->match_comp_cnt != 0), + sdp_state->match_comp_cnt, + sdp_state->ice_mismatch, + sdp_state->ice_restart, + pj_ice_sess_role_name(sdp_state->local_role))); + + return PJ_SUCCESS; + +} + + +/* Verify incoming offer and create initial answer */ +static pj_status_t create_initial_answer(struct transport_ice *tp_ice, + pj_pool_t *sdp_pool, + pjmedia_sdp_session *loc_sdp, + const pjmedia_sdp_session *rem_sdp, + unsigned media_index) +{ + const pjmedia_sdp_media *rem_m = rem_sdp->media[media_index]; + pj_status_t status; + + /* Check if media is removed (just in case) */ + if (rem_m->desc.port == 0) { + return PJ_SUCCESS; + } + + /* Verify the offer */ + status = verify_ice_sdp(tp_ice, sdp_pool, rem_sdp, media_index, + PJ_ICE_SESS_ROLE_CONTROLLED, + &tp_ice->rem_offer_state); + if (status != PJ_SUCCESS) { + set_no_ice(tp_ice, "Invalid SDP offer", status); + return status; + } + + /* Does remote support ICE? */ + if (tp_ice->rem_offer_state.match_comp_cnt==0) { + set_no_ice(tp_ice, "No ICE found in SDP offer", PJ_SUCCESS); + return PJ_SUCCESS; + } + + /* ICE ice_mismatch? */ + if (tp_ice->rem_offer_state.ice_mismatch) { + set_no_ice(tp_ice, "ICE ice_mismatch in remote offer", PJ_SUCCESS); + encode_ice_mismatch(sdp_pool, loc_sdp, media_index); + return PJ_SUCCESS; + } + + /* Encode ICE in SDP */ + status = encode_session_in_sdp(tp_ice, sdp_pool, loc_sdp, media_index, + tp_ice->rem_offer_state.match_comp_cnt, + PJ_FALSE); + if (status != PJ_SUCCESS) { + set_no_ice(tp_ice, "Error encoding SDP answer", status); + return status; + } + + return PJ_SUCCESS; +} + + +/* Create subsequent SDP offer */ +static pj_status_t create_subsequent_offer(struct transport_ice *tp_ice, + pj_pool_t *sdp_pool, + pjmedia_sdp_session *loc_sdp, + unsigned media_index) +{ + unsigned comp_cnt; + + if (pj_ice_strans_has_sess(tp_ice->ice_st) == PJ_FALSE) { + /* We don't have ICE */ + return PJ_SUCCESS; + } + + comp_cnt = pj_ice_strans_get_running_comp_cnt(tp_ice->ice_st); + return encode_session_in_sdp(tp_ice, sdp_pool, loc_sdp, media_index, + comp_cnt, PJ_FALSE); +} + + +/* Create subsequent SDP answer */ +static pj_status_t create_subsequent_answer(struct transport_ice *tp_ice, + pj_pool_t *sdp_pool, + pjmedia_sdp_session *loc_sdp, + const pjmedia_sdp_session *rem_sdp, + unsigned media_index) +{ + pj_status_t status; + + /* We have a session */ + status = verify_ice_sdp(tp_ice, sdp_pool, rem_sdp, media_index, + PJ_ICE_SESS_ROLE_CONTROLLED, + &tp_ice->rem_offer_state); + if (status != PJ_SUCCESS) { + /* Something wrong with the offer */ + return status; + } + + if (pj_ice_strans_has_sess(tp_ice->ice_st)) { + /* + * Received subsequent offer while we have ICE active. + */ + + if (tp_ice->rem_offer_state.match_comp_cnt == 0) { + /* Remote no longer offers ICE */ + return PJ_SUCCESS; + } + + if (tp_ice->rem_offer_state.ice_mismatch) { + encode_ice_mismatch(sdp_pool, loc_sdp, media_index); + return PJ_SUCCESS; + } + + status = encode_session_in_sdp(tp_ice, sdp_pool, loc_sdp, media_index, + tp_ice->rem_offer_state.match_comp_cnt, + tp_ice->rem_offer_state.ice_restart); + if (status != PJ_SUCCESS) + return status; + + /* Done */ + + } else { + /* + * Received subsequent offer while we DON'T have ICE active. + */ + + if (tp_ice->rem_offer_state.match_comp_cnt == 0) { + /* Remote does not support ICE */ + return PJ_SUCCESS; + } + + if (tp_ice->rem_offer_state.ice_mismatch) { + encode_ice_mismatch(sdp_pool, loc_sdp, media_index); + return PJ_SUCCESS; + } + + /* Looks like now remote is offering ICE, so we need to create + * ICE session now. + */ + status = pj_ice_strans_init_ice(tp_ice->ice_st, + PJ_ICE_SESS_ROLE_CONTROLLED, + NULL, NULL); + if (status != PJ_SUCCESS) { + /* Fail to create new ICE session */ + return status; + } + + status = encode_session_in_sdp(tp_ice, sdp_pool, loc_sdp, media_index, + tp_ice->rem_offer_state.match_comp_cnt, + tp_ice->rem_offer_state.ice_restart); + if (status != PJ_SUCCESS) + return status; + + /* Done */ + } + + return PJ_SUCCESS; } /* - * Start ICE checks when both offer and answer are available. + * For both UAC and UAS, pass in the SDP before sending it to remote. + * This will add ICE attributes to the SDP. */ -static pj_status_t transport_media_start(pjmedia_transport *tp, - pj_pool_t *pool, - pjmedia_sdp_session *sdp_local, - const pjmedia_sdp_session *rem_sdp, - unsigned media_index) +static pj_status_t transport_media_create(pjmedia_transport *tp, + pj_pool_t *sdp_pool, + unsigned options, + const pjmedia_sdp_session *rem_sdp, + unsigned media_index) { struct transport_ice *tp_ice = (struct transport_ice*)tp; - const pjmedia_sdp_attr *attr; - unsigned i, cand_cnt; - pj_ice_sess_cand *cand; - const pjmedia_sdp_media *sdp_med; - pj_bool_t remote_is_lite = PJ_FALSE; - pj_bool_t ice_mismatch = PJ_FALSE; - pjmedia_sdp_conn *conn = NULL; - pj_sockaddr conn_addr; - pj_bool_t conn_found_in_candidate = PJ_FALSE; - pj_str_t uname, pass; + pj_ice_sess_role ice_role; pj_status_t status; - PJ_ASSERT_RETURN(tp && pool && rem_sdp, PJ_EINVAL); - PJ_ASSERT_RETURN(media_index < rem_sdp->media_count, PJ_EINVAL); + PJ_UNUSED_ARG(media_index); + PJ_UNUSED_ARG(sdp_pool); - sdp_med = rem_sdp->media[media_index]; + tp_ice->media_option = options; + tp_ice->oa_role = ROLE_NONE; + tp_ice->initial_sdp = PJ_TRUE; + + /* Init ICE, the initial role is set now based on availability of + * rem_sdp, but it will be checked again later. + */ + ice_role = (rem_sdp==NULL ? PJ_ICE_SESS_ROLE_CONTROLLING : + PJ_ICE_SESS_ROLE_CONTROLLED); + status = pj_ice_strans_init_ice(tp_ice->ice_st, ice_role, NULL, NULL); + + /* Done */ + return status; +} + + +static pj_status_t transport_encode_sdp(pjmedia_transport *tp, + pj_pool_t *sdp_pool, + pjmedia_sdp_session *sdp_local, + const pjmedia_sdp_session *rem_sdp, + unsigned media_index) +{ + struct transport_ice *tp_ice = (struct transport_ice*)tp; + pj_status_t status; /* Validate media transport */ - /* By now, this transport only support RTP/AVP transport */ + /* For now, this transport only support RTP/AVP transport */ if ((tp_ice->media_option & PJMEDIA_TPMED_NO_TRANSPORT_CHECKING) == 0) { - pjmedia_sdp_media *m_rem, *m_loc; + pjmedia_sdp_media *loc_m, *rem_m; - m_rem = rem_sdp->media[media_index]; - m_loc = sdp_local->media[media_index]; + rem_m = rem_sdp? rem_sdp->media[media_index] : NULL; + loc_m = sdp_local->media[media_index]; - if (pj_stricmp(&m_loc->desc.transport, &ID_RTP_AVP) || - (pj_stricmp(&m_rem->desc.transport, &ID_RTP_AVP))) + if (pj_stricmp(&loc_m->desc.transport, &STR_RTP_AVP) || + (rem_m && pj_stricmp(&rem_m->desc.transport, &STR_RTP_AVP))) { - pjmedia_sdp_media_deactivate(pool, m_loc); + pjmedia_sdp_media_deactivate(sdp_pool, loc_m); return PJMEDIA_SDP_EINPROTO; } } - /* Get the SDP connection for the media stream. - * We'll verify later if the SDP connection address is specified - * as one of the candidate. - */ - conn = sdp_med->conn; - if (conn == NULL) - conn = rem_sdp->conn; + if (tp_ice->initial_sdp) { + if (rem_sdp) { + status = create_initial_answer(tp_ice, sdp_pool, sdp_local, + rem_sdp, media_index); + } else { + status = create_initial_offer(tp_ice, sdp_pool, sdp_local, + media_index); + } + } else { + if (rem_sdp) { + status = create_subsequent_answer(tp_ice, sdp_pool, sdp_local, + rem_sdp, media_index); + } else { + status = create_subsequent_offer(tp_ice, sdp_pool, sdp_local, + media_index); + } + } - if (conn == NULL) { - /* Unable to find SDP connection */ - return PJMEDIA_SDP_EMISSINGCONN; + if (status==PJ_SUCCESS) { + if (rem_sdp) + tp_ice->oa_role = ROLE_ANSWERER; + else + tp_ice->oa_role = ROLE_OFFERER; } - pj_sockaddr_in_init(&conn_addr.ipv4, &conn->addr, - (pj_uint16_t)sdp_med->desc.port); + return status; +} - /* Find ice-ufrag attribute in media descriptor */ - attr = pjmedia_sdp_attr_find2(sdp_med->attr_count, sdp_med->attr, - "ice-ufrag", NULL); - if (attr == NULL) { - /* Find ice-ufrag attribute in session descriptor */ - attr = pjmedia_sdp_attr_find2(rem_sdp->attr_count, rem_sdp->attr, - "ice-ufrag", NULL); - if (attr == NULL) { - set_no_ice(tp_ice, "ice-ufrag attribute not found"); - return PJ_SUCCESS; - } - } - uname = attr->value; - /* Find ice-pwd attribute in media descriptor */ - attr = pjmedia_sdp_attr_find2(sdp_med->attr_count, sdp_med->attr, - "ice-pwd", NULL); - if (attr == NULL) { - /* Find ice-pwd attribute in session descriptor */ - attr = pjmedia_sdp_attr_find2(rem_sdp->attr_count, rem_sdp->attr, - "ice-pwd", NULL); - if (attr == NULL) { - set_no_ice(tp_ice, "ice-pwd attribute not found"); - return PJ_SUCCESS; - } - } - pass = attr->value; +/* Start ICE session with the specified remote SDP */ +static pj_status_t start_ice(struct transport_ice *tp_ice, + pj_pool_t *tmp_pool, + const pjmedia_sdp_session *rem_sdp, + unsigned media_index) +{ + pjmedia_sdp_media *rem_m = rem_sdp->media[media_index]; + pjmedia_sdp_attr *ufrag_attr, *pwd_attr; + pj_ice_sess_cand *cand; + unsigned i, cand_cnt; + pj_status_t status; + + get_ice_attr(rem_sdp, rem_m, &ufrag_attr, &pwd_attr); /* Allocate candidate array */ cand = (pj_ice_sess_cand*) - pj_pool_calloc(pool, PJ_ICE_MAX_CAND, sizeof(pj_ice_sess_cand)); + pj_pool_calloc(tmp_pool, PJ_ICE_MAX_CAND, + sizeof(pj_ice_sess_cand)); /* Get all candidates in the media */ cand_cnt = 0; - for (i=0; i<sdp_med->attr_count && cand_cnt < PJ_ICE_MAX_CAND; ++i) { + for (i=0; i<rem_m->attr_count && cand_cnt < PJ_ICE_MAX_CAND; ++i) { pjmedia_sdp_attr *attr; - attr = sdp_med->attr[i]; + attr = rem_m->attr[i]; - /* Detect if remote is ICE lite */ - if (pj_stricmp(&attr->name, &STR_ICE_LITE)==0) { - remote_is_lite = PJ_TRUE; + if (pj_strcmp(&attr->name, &STR_CANDIDATE)!=0) continue; - } - /* Detect if remote has reported ICE mismatch */ - if (pj_stricmp(&attr->name, &STR_ICE_MISMATCH)==0) { - ice_mismatch = PJ_TRUE; + /* Parse candidate */ + status = parse_cand(tp_ice->base.name, tmp_pool, &attr->value, + &cand[cand_cnt]); + if (status != PJ_SUCCESS) { + PJ_LOG(4,(tp_ice->base.name, + "Error in parsing SDP candidate attribute '%.*s', " + "candidate is ignored", + (int)attr->value.slen, attr->value.ptr)); continue; } - if (pj_stricmp(&attr->name, &STR_CANDIDATE)!=0) - continue; + cand_cnt++; + } - /* Parse candidate */ - status = parse_cand(pool, &attr->value, &cand[cand_cnt]); + /* Start ICE */ + return pj_ice_strans_start_ice(tp_ice->ice_st, &ufrag_attr->value, + &pwd_attr->value, cand_cnt, cand); +} + + +/* + * Start ICE checks when both offer and answer have been negotiated + * by SDP negotiator. + */ +static pj_status_t transport_media_start(pjmedia_transport *tp, + pj_pool_t *tmp_pool, + const pjmedia_sdp_session *sdp_local, + const pjmedia_sdp_session *rem_sdp, + unsigned media_index) +{ + struct transport_ice *tp_ice = (struct transport_ice*)tp; + pjmedia_sdp_media *rem_m; + enum oa_role current_oa_role; + pj_bool_t initial_oa; + pj_status_t status; + + PJ_ASSERT_RETURN(tp && tmp_pool && rem_sdp, PJ_EINVAL); + PJ_ASSERT_RETURN(media_index < rem_sdp->media_count, PJ_EINVAL); + + rem_m = rem_sdp->media[media_index]; + + initial_oa = tp_ice->initial_sdp; + current_oa_role = tp_ice->oa_role; + + /* SDP has been negotiated */ + tp_ice->initial_sdp = PJ_FALSE; + tp_ice->oa_role = ROLE_NONE; + + /* Nothing to do if we don't have ICE session */ + if (pj_ice_strans_has_sess(tp_ice->ice_st) == PJ_FALSE) { + return PJ_SUCCESS; + } + + /* Processing depends on the offer/answer role */ + if (current_oa_role == ROLE_OFFERER) { + /* + * We are offerer. So this will be the first time we see the + * remote's SDP. + */ + struct sdp_state answer_state; + + /* Verify the answer */ + status = verify_ice_sdp(tp_ice, tmp_pool, rem_sdp, media_index, + PJ_ICE_SESS_ROLE_CONTROLLING, &answer_state); if (status != PJ_SUCCESS) { - PJ_LOG(4,(THIS_FILE, - "Error in parsing SDP candidate attribute, " - "candidate is ignored")); - continue; + /* Something wrong in the SDP answer */ + set_no_ice(tp_ice, "Invalid remote SDP answer", status); + return status; } - /* Check if this candidate is equal to the connection line */ - if (!conn_found_in_candidate && - pj_memcmp(&conn_addr.ipv4, &cand[cand_cnt].addr.ipv4, - sizeof(pj_sockaddr_in))==0) + /* Does it have ICE? */ + if (answer_state.match_comp_cnt == 0) { + /* Remote doesn't support ICE */ + set_no_ice(tp_ice, "Remote answer doesn't support ICE", + PJ_SUCCESS); + return PJ_SUCCESS; + } + + /* Check if remote has reported ice-mismatch */ + if (pjmedia_sdp_attr_find(rem_m->attr_count, rem_m->attr, + &STR_ICE_MISMATCH, NULL) != NULL) { - conn_found_in_candidate = PJ_TRUE; + /* Remote has reported ice-mismatch */ + set_no_ice(tp_ice, + "Remote answer contains 'ice-mismatch' attribute", + PJ_SUCCESS); + return PJ_SUCCESS; } - cand_cnt++; - } + /* Check if remote has indicated a restart */ + if (answer_state.ice_restart) { + PJ_LOG(2,(tp_ice->base.name, + "Warning: remote has signalled ICE restart in SDP " + "answer which is disallowed. Remote ICE negotiation" + " may fail.")); + } - /* Handle ice-mismatch case */ - if (ice_mismatch) { - set_no_ice(tp_ice, "remote reported ice-mismatch"); - return PJ_SUCCESS; - } + /* Check if the answer itself is mismatched */ + if (answer_state.ice_mismatch) { + /* This happens either when a B2BUA modified remote answer but + * strangely didn't modify our offer, or remote is not capable + * of detecting mismatch in our offer (it didn't put + * 'ice-mismatch' attribute in the answer). + */ + PJ_LOG(2,(tp_ice->base.name, + "Warning: remote answer mismatch, but it does not " + "reject our offer with 'ice-mismatch'. ICE negotiation " + "may fail")); + } - /* Handle case where SDP connection address is not specified as - * one of the candidate. - */ - if (!conn_found_in_candidate) { - set_no_ice(tp_ice, "local reported ice-mismatch"); - return PJ_SUCCESS; + /* Do nothing if ICE is complete or running */ + if (pj_ice_strans_sess_is_running(tp_ice->ice_st)) { + PJ_LOG(4,(tp_ice->base.name, + "Ignored offer/answer because ICE is running")); + return PJ_SUCCESS; + } + + if (pj_ice_strans_sess_is_complete(tp_ice->ice_st)) { + PJ_LOG(4,(tp_ice->base.name, "ICE session unchanged")); + return PJ_SUCCESS; + } + + /* Start ICE */ + + } else { + /* + * We are answerer. We've seen and negotiated remote's SDP + * before, and the result is in "rem_offer_state". + */ + const pjmedia_sdp_attr *ufrag_attr, *pwd_attr; + + /* Check for ICE in remote offer */ + if (tp_ice->rem_offer_state.match_comp_cnt == 0) { + /* No ICE attribute present */ + set_no_ice(tp_ice, "Remote no longer offers ICE", + PJ_SUCCESS); + return PJ_SUCCESS; + } + + /* Check for ICE ice_mismatch condition in the offer */ + if (tp_ice->rem_offer_state.ice_mismatch) { + set_no_ice(tp_ice, "Remote offer mismatch: ", + PJNATH_EICEMISMATCH); + return PJ_SUCCESS; + } + + /* If ICE is complete and remote doesn't request restart, + * then leave the session as is. + */ + if (!initial_oa && tp_ice->rem_offer_state.ice_restart == PJ_FALSE) { + /* Remote has not requested ICE restart, so session is + * unchanged. + */ + PJ_LOG(4,(tp_ice->base.name, "ICE session unchanged")); + return PJ_SUCCESS; + } + + /* Either remote has requested ICE restart or this is our + * first answer. + */ + + /* Stop ICE */ + if (!initial_oa) { + set_no_ice(tp_ice, "restarting by remote request..", PJ_SUCCESS); + + /* We have put new ICE ufrag and pwd in the answer. Now + * create a new ICE session with that ufrag/pwd pair. + */ + get_ice_attr(sdp_local, sdp_local->media[media_index], + &ufrag_attr, &pwd_attr); + status = pj_ice_strans_init_ice(tp_ice->ice_st, + tp_ice->rem_offer_state.local_role, + &ufrag_attr->value, + &pwd_attr->value); + if (status != PJ_SUCCESS) { + PJ_LOG(1,(tp_ice->base.name, + "ICE re-initialization failed (status=%d)!", + status)); + return status; + } + } + + /* start ICE */ } - /* If our role was controlled but it turns out that remote is - * a lite implementation, change our role to controlling. - */ - if (remote_is_lite && - pj_ice_strans_get_role(tp_ice->ice_st) == PJ_ICE_SESS_ROLE_CONTROLLED) - { - pj_ice_strans_change_role(tp_ice->ice_st, - PJ_ICE_SESS_ROLE_CONTROLLING); + /* Now start ICE */ + status = start_ice(tp_ice, tmp_pool, rem_sdp, media_index); + if (status != PJ_SUCCESS) { + PJ_LOG(1,(tp_ice->base.name, + "ICE restart failed (status=%d)!", + status)); + return status; } - /* Start ICE */ - return pj_ice_strans_start_ice(tp_ice->ice_st, &uname, &pass, - cand_cnt, cand); + /* Done */ + + return PJ_SUCCESS; } static pj_status_t transport_media_stop(pjmedia_transport *tp) { struct transport_ice *tp_ice = (struct transport_ice*)tp; - return pj_ice_strans_stop_ice(tp_ice->ice_st); + + set_no_ice(tp_ice, "media stop requested", PJ_SUCCESS); + + return PJ_SUCCESS; } diff --git a/pjmedia/src/pjmedia/transport_srtp.c b/pjmedia/src/pjmedia/transport_srtp.c index 6f3c06fc..c69296bd 100644 --- a/pjmedia/src/pjmedia/transport_srtp.c +++ b/pjmedia/src/pjmedia/transport_srtp.c @@ -91,6 +91,10 @@ typedef struct transport_srtp pjmedia_srtp_crypto tx_policy; pjmedia_srtp_crypto rx_policy; + /* Temporary policy for negotiation */ + pjmedia_srtp_crypto tx_policy_neg; + pjmedia_srtp_crypto rx_policy_neg; + /* libSRTP contexts */ srtp_t srtp_tx_ctx; srtp_t srtp_rx_ctx; @@ -105,7 +109,15 @@ typedef struct transport_srtp pj_ssize_t size); /* Transport information */ - pjmedia_transport *real_tp; /**< Underlying transport. */ + pjmedia_transport *member_tp; /**< Underlying transport. */ + + /* When in SRTP optional mode, instead of always offering RTP/AVP, + * we should create offer based on remote preference. At the first time, + * remote preference is unknown, it is known after media_start() called. + * So next time the same session need to create an offer, it will create + * SDP with transport type based on remote preference. + */ + pj_bool_t remote_prefer_rtp_savp; } transport_srtp; @@ -151,14 +163,18 @@ static pj_status_t transport_send_rtcp2(pjmedia_transport *tp, const void *pkt, pj_size_t size); static pj_status_t transport_media_create(pjmedia_transport *tp, - pj_pool_t *pool, + pj_pool_t *sdp_pool, unsigned options, + const pjmedia_sdp_session *sdp_remote, + unsigned media_index); +static pj_status_t transport_encode_sdp(pjmedia_transport *tp, + pj_pool_t *sdp_pool, pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *sdp_remote, unsigned media_index); static pj_status_t transport_media_start (pjmedia_transport *tp, pj_pool_t *pool, - pjmedia_sdp_session *sdp_local, + const pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *sdp_remote, unsigned media_index); static pj_status_t transport_media_stop(pjmedia_transport *tp); @@ -178,6 +194,7 @@ static pjmedia_transport_op transport_srtp_op = &transport_send_rtcp, &transport_send_rtcp2, &transport_media_create, + &transport_encode_sdp, &transport_media_start, &transport_media_stop, &transport_simulate_lost, @@ -331,6 +348,7 @@ PJ_DEF(pj_status_t) pjmedia_transport_srtp_create( srtp->pool = pool; srtp->session_inited = PJ_FALSE; srtp->bypass_srtp = PJ_FALSE; + srtp->remote_prefer_rtp_savp = PJ_FALSE; if (opt) { srtp->setting = *opt; @@ -367,7 +385,7 @@ PJ_DEF(pj_status_t) pjmedia_transport_srtp_create( srtp->base.op = &transport_srtp_op; /* Set underlying transport */ - srtp->real_tp = tp; + srtp->member_tp = tp; /* Done */ *p_tp = &srtp->base; @@ -457,8 +475,7 @@ PJ_DEF(pj_status_t) pjmedia_transport_srtp_start( } srtp->tx_policy = *tx; pj_strset(&srtp->tx_policy.key, srtp->tx_key, tx->key.slen); - srtp->tx_policy.name = - pj_str(crypto_suites[get_crypto_idx(&tx->name)].name); + srtp->tx_policy.name=pj_str(crypto_suites[get_crypto_idx(&tx->name)].name); /* Init receive direction */ @@ -491,8 +508,7 @@ PJ_DEF(pj_status_t) pjmedia_transport_srtp_start( } srtp->rx_policy = *rx; pj_strset(&srtp->rx_policy.key, srtp->rx_key, rx->key.slen); - srtp->rx_policy.name = - pj_str(crypto_suites[get_crypto_idx(&rx->name)].name); + srtp->rx_policy.name=pj_str(crypto_suites[get_crypto_idx(&rx->name)].name); /* Declare SRTP session initialized */ srtp->session_inited = PJ_TRUE; @@ -550,7 +566,7 @@ PJ_DEF(pjmedia_transport *) pjmedia_transport_srtp_get_member( PJ_ASSERT_RETURN(tp, NULL); - return srtp->real_tp; + return srtp->member_tp; } @@ -577,7 +593,7 @@ static pj_status_t transport_get_info(pjmedia_transport *tp, pj_memcpy(&info->spc_info[spc_info_idx].buffer, &srtp_info, sizeof(srtp_info)); - return pjmedia_transport_get_info(srtp->real_tp, info); + return pjmedia_transport_get_info(srtp->member_tp, info); } static pj_status_t transport_attach(pjmedia_transport *tp, @@ -594,7 +610,7 @@ static pj_status_t transport_attach(pjmedia_transport *tp, pj_status_t status; /* Attach itself to transport */ - status = pjmedia_transport_attach(srtp->real_tp, srtp, rem_addr, rem_rtcp, + status = pjmedia_transport_attach(srtp->member_tp, srtp, rem_addr, rem_rtcp, addr_len, &srtp_rtp_cb, &srtp_rtcp_cb); if (status != PJ_SUCCESS) return status; @@ -614,8 +630,8 @@ static void transport_detach(pjmedia_transport *tp, void *strm) PJ_UNUSED_ARG(strm); PJ_ASSERT_ON_FAIL(tp, return); - if (srtp->real_tp) { - pjmedia_transport_detach(srtp->real_tp, srtp); + if (srtp->member_tp) { + pjmedia_transport_detach(srtp->member_tp, srtp); } /* Clear up application infos from transport */ @@ -634,7 +650,7 @@ static pj_status_t transport_send_rtp( pjmedia_transport *tp, err_status_t err; if (srtp->bypass_srtp) - return pjmedia_transport_send_rtp(srtp->real_tp, pkt, size); + return pjmedia_transport_send_rtp(srtp->member_tp, pkt, size); if (!srtp->session_inited) return PJ_SUCCESS; @@ -647,7 +663,7 @@ static pj_status_t transport_send_rtp( pjmedia_transport *tp, err = srtp_protect(srtp->srtp_tx_ctx, srtp->tx_buffer, &len); if (err == err_status_ok) { - status = pjmedia_transport_send_rtp(srtp->real_tp, srtp->tx_buffer, len); + status = pjmedia_transport_send_rtp(srtp->member_tp, srtp->tx_buffer, len); } else { status = PJMEDIA_ERRNO_FROM_LIBSRTP(err); } @@ -676,7 +692,7 @@ static pj_status_t transport_send_rtcp2(pjmedia_transport *tp, err_status_t err; if (srtp->bypass_srtp) { - return pjmedia_transport_send_rtcp2(srtp->real_tp, addr, addr_len, + return pjmedia_transport_send_rtcp2(srtp->member_tp, addr, addr_len, pkt, size); } @@ -692,7 +708,7 @@ static pj_status_t transport_send_rtcp2(pjmedia_transport *tp, err = srtp_protect_rtcp(srtp->srtp_tx_ctx, srtp->tx_buffer, &len); if (err == err_status_ok) { - status = pjmedia_transport_send_rtcp2(srtp->real_tp, addr, addr_len, + status = pjmedia_transport_send_rtcp2(srtp->member_tp, addr, addr_len, srtp->tx_buffer, len); } else { status = PJMEDIA_ERRNO_FROM_LIBSRTP(err); @@ -710,7 +726,7 @@ static pj_status_t transport_simulate_lost(pjmedia_transport *tp, { transport_srtp *srtp = (transport_srtp *) tp; - return pjmedia_transport_simulate_lost(srtp->real_tp, dir, pct_lost); + return pjmedia_transport_simulate_lost(srtp->member_tp, dir, pct_lost); } static pj_status_t transport_destroy (pjmedia_transport *tp) @@ -722,8 +738,8 @@ static pj_status_t transport_destroy (pjmedia_transport *tp) pjmedia_transport_detach(tp, NULL); - if (srtp->setting.close_member_tp && srtp->real_tp) { - pjmedia_transport_close(srtp->real_tp); + if (srtp->setting.close_member_tp && srtp->member_tp) { + pjmedia_transport_close(srtp->member_tp); } status = pjmedia_transport_srtp_stop(tp); @@ -955,13 +971,75 @@ static pj_status_t parse_attr_crypto(pj_pool_t *pool, } static pj_status_t transport_media_create(pjmedia_transport *tp, - pj_pool_t *pool, + pj_pool_t *sdp_pool, unsigned options, - pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *sdp_remote, unsigned media_index) { struct transport_srtp *srtp = (struct transport_srtp*) tp; + unsigned member_tp_option; + + PJ_ASSERT_RETURN(tp, PJ_EINVAL); + + pj_bzero(&srtp->rx_policy_neg, sizeof(srtp->rx_policy_neg)); + pj_bzero(&srtp->tx_policy_neg, sizeof(srtp->tx_policy_neg)); + + srtp->media_option = options; + member_tp_option = options | PJMEDIA_TPMED_NO_TRANSPORT_CHECKING; + + srtp->offerer_side = sdp_remote == NULL; + + /* Validations */ + if (srtp->offerer_side) { + + if (srtp->setting.use == PJMEDIA_SRTP_DISABLED) + goto BYPASS_SRTP; + + } else { + + pjmedia_sdp_media *m_rem; + + m_rem = sdp_remote->media[media_index]; + + /* Nothing to do on inactive media stream */ + if (pjmedia_sdp_media_find_attr(m_rem, &ID_INACTIVE, NULL)) + goto BYPASS_SRTP; + + /* Validate remote media transport based on SRTP usage option. + */ + switch (srtp->setting.use) { + case PJMEDIA_SRTP_DISABLED: + if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) == 0) + return PJMEDIA_SRTP_ESDPINTRANSPORT; + goto BYPASS_SRTP; + case PJMEDIA_SRTP_OPTIONAL: + break; + case PJMEDIA_SRTP_MANDATORY: + if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) != 0) + return PJMEDIA_SRTP_ESDPINTRANSPORT; + break; + } + + } + goto PROPAGATE_MEDIA_CREATE; + +BYPASS_SRTP: + srtp->bypass_srtp = PJ_TRUE; + member_tp_option &= ~PJMEDIA_TPMED_NO_TRANSPORT_CHECKING; + +PROPAGATE_MEDIA_CREATE: + return pjmedia_transport_media_create(srtp->member_tp, sdp_pool, + member_tp_option, sdp_remote, + media_index); +} + +static pj_status_t transport_encode_sdp(pjmedia_transport *tp, + pj_pool_t *sdp_pool, + pjmedia_sdp_session *sdp_local, + const pjmedia_sdp_session *sdp_remote, + unsigned media_index) +{ + struct transport_srtp *srtp = (struct transport_srtp*) tp; pjmedia_sdp_media *m_rem, *m_loc; enum { MAXLEN = 512 }; char buffer[MAXLEN]; @@ -970,20 +1048,15 @@ static pj_status_t transport_media_create(pjmedia_transport *tp, pjmedia_sdp_attr *attr; pj_str_t attr_value; unsigned i, j; - unsigned member_tp_option; - PJ_ASSERT_RETURN(tp && pool && sdp_local, PJ_EINVAL); + PJ_ASSERT_RETURN(tp && sdp_pool && sdp_local, PJ_EINVAL); - srtp->media_option = options; - member_tp_option = options | PJMEDIA_TPMED_NO_TRANSPORT_CHECKING; - - pj_bzero(&srtp->rx_policy, sizeof(srtp->tx_policy)); - pj_bzero(&srtp->tx_policy, sizeof(srtp->rx_policy)); + srtp->offerer_side = sdp_remote == NULL; m_rem = sdp_remote ? sdp_remote->media[media_index] : NULL; m_loc = sdp_local->media[media_index]; - /* bypass if media transport is not RTP/AVP or RTP/SAVP */ + /* Bypass if media transport is not RTP/AVP or RTP/SAVP */ if (pj_stricmp(&m_loc->desc.transport, &ID_RTP_AVP) != 0 && pj_stricmp(&m_loc->desc.transport, &ID_RTP_SAVP) != 0) goto BYPASS_SRTP; @@ -991,195 +1064,209 @@ static pj_status_t transport_media_create(pjmedia_transport *tp, /* If the media is inactive, do nothing. */ if (pjmedia_sdp_media_find_attr(m_loc, &ID_INACTIVE, NULL) || (m_rem && pjmedia_sdp_media_find_attr(m_rem, &ID_INACTIVE, NULL))) - { goto BYPASS_SRTP; - } - - srtp->offerer_side = !sdp_remote; /* Check remote media transport & set local media transport * based on SRTP usage option. */ if (srtp->offerer_side) { - if (srtp->setting.use == PJMEDIA_SRTP_DISABLED) { - goto BYPASS_SRTP; - } else if (srtp->setting.use == PJMEDIA_SRTP_OPTIONAL) { - m_loc->desc.transport = ID_RTP_AVP; - } else if (srtp->setting.use == PJMEDIA_SRTP_MANDATORY) { - m_loc->desc.transport = ID_RTP_SAVP; + + /* Generate transport */ + switch (srtp->setting.use) { + case PJMEDIA_SRTP_DISABLED: + goto BYPASS_SRTP; + case PJMEDIA_SRTP_OPTIONAL: + m_loc->desc.transport = srtp->remote_prefer_rtp_savp? + ID_RTP_SAVP : ID_RTP_AVP; + break; + case PJMEDIA_SRTP_MANDATORY: + m_loc->desc.transport = ID_RTP_SAVP; + break; } - } else { - if (srtp->setting.use == PJMEDIA_SRTP_DISABLED) { - if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) == 0) { - DEACTIVATE_MEDIA(pool, m_loc); - return PJMEDIA_SRTP_ESDPINTRANSPORT; + + /* Generate crypto attribute if not yet */ + if (pjmedia_sdp_media_find_attr(m_loc, &ID_CRYPTO, NULL) == NULL) { + for (i=0; i<srtp->setting.crypto_count; ++i) { + /* Offer crypto-suites based on setting. */ + buffer_len = MAXLEN; + status = generate_crypto_attr_value(srtp->pool, buffer, &buffer_len, + &srtp->setting.crypto[i], + i+1); + if (status != PJ_SUCCESS) + return status; + + /* If buffer_len==0, just skip the crypto attribute. */ + if (buffer_len) { + pj_strset(&attr_value, buffer, buffer_len); + attr = pjmedia_sdp_attr_create(srtp->pool, ID_CRYPTO.ptr, + &attr_value); + m_loc->attr[m_loc->attr_count++] = attr; + } } - goto BYPASS_SRTP; - } else if (srtp->setting.use == PJMEDIA_SRTP_OPTIONAL) { + } + + } else { + /* Answerer side */ + + pj_assert(sdp_remote && m_rem); + + /* Generate transport */ + switch (srtp->setting.use) { + case PJMEDIA_SRTP_DISABLED: + if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) == 0) + return PJMEDIA_SRTP_ESDPINTRANSPORT; + goto BYPASS_SRTP; + case PJMEDIA_SRTP_OPTIONAL: m_loc->desc.transport = m_rem->desc.transport; - } else if (srtp->setting.use == PJMEDIA_SRTP_MANDATORY) { - if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) != 0) { - DEACTIVATE_MEDIA(pool, m_loc); - return PJMEDIA_SRTP_ESDPINTRANSPORT; - } - m_loc->desc.transport = ID_RTP_SAVP; + break; + case PJMEDIA_SRTP_MANDATORY: + if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) != 0) + return PJMEDIA_SRTP_ESDPINTRANSPORT; + m_loc->desc.transport = ID_RTP_SAVP; + break; } - } - /* Generate crypto attribute */ - if (srtp->offerer_side) { - for (i=0; i<srtp->setting.crypto_count; ++i) { - /* Offer crypto-suites based on setting. */ + /* Generate crypto attribute if not yet */ + if (pjmedia_sdp_media_find_attr(m_loc, &ID_CRYPTO, NULL) == NULL) { + + pjmedia_srtp_crypto tmp_rx_crypto; + pj_bool_t has_crypto_attr = PJ_FALSE; + int matched_idx = -1; + int chosen_tag = 0; + int tags[64]; /* assume no more than 64 crypto attrs in a media */ + int cr_attr_count = 0; + + /* Find supported crypto-suite, get the tag, and assign policy_local */ + for (i=0; i<m_rem->attr_count; ++i) { + if (pj_stricmp(&m_rem->attr[i]->name, &ID_CRYPTO) != 0) + continue; + + has_crypto_attr = PJ_TRUE; + + status = parse_attr_crypto(srtp->pool, m_rem->attr[i], + &tmp_rx_crypto, &tags[cr_attr_count]); + if (status != PJ_SUCCESS) + return status; + + /* Check duplicated tag */ + for (j=0; j<cr_attr_count; ++j) { + if (tags[j] == tags[cr_attr_count]) { + DEACTIVATE_MEDIA(sdp_pool, m_loc); + return PJMEDIA_SRTP_ESDPDUPCRYPTOTAG; + } + } + + if (matched_idx == -1) { + /* lets see if the crypto-suite offered is supported */ + for (j=0; j<srtp->setting.crypto_count; ++j) + if (pj_stricmp(&tmp_rx_crypto.name, + &srtp->setting.crypto[j].name) == 0) + { + int cs_idx = get_crypto_idx(&tmp_rx_crypto.name); + + /* Force to use test key */ + /* bad keys for snom: */ + //char *hex_test_key = "58b29c5c8f42308120ce857e439f2d" + // "7810a8b10ad0b1446be5470faea496"; + //char *hex_test_key = "20a26aac7ba062d356ff52b61e3993" + // "ccb78078f12c64db94b9c294927fd0"; + //pj_str_t *test_key = &srtp->setting.crypto[j].key; + //char *raw_test_key = pj_pool_zalloc(srtp->pool, 64); + //hex_string_to_octet_string( + // raw_test_key, + // hex_test_key, + // strlen(hex_test_key)); + //pj_strset(test_key, raw_test_key, + // crypto_suites[cs_idx].cipher_key_len); + /* EO Force to use test key */ + + if (tmp_rx_crypto.key.slen != + (int)crypto_suites[cs_idx].cipher_key_len) + return PJMEDIA_SRTP_EINKEYLEN; + + srtp->rx_policy_neg = tmp_rx_crypto; + chosen_tag = tags[cr_attr_count]; + matched_idx = j; + break; + } + } + cr_attr_count++; + } + + /* Check crypto negotiation result */ + switch (srtp->setting.use) { + case PJMEDIA_SRTP_DISABLED: + pj_assert(!"Should never reach here"); + break; + + case PJMEDIA_SRTP_OPTIONAL: + /* bypass SRTP when no crypto-attr and remote uses RTP/AVP */ + if (!has_crypto_attr && + pj_stricmp(&m_rem->desc.transport, &ID_RTP_AVP) == 0) + goto BYPASS_SRTP; + /* bypass SRTP when nothing match and remote uses RTP/AVP */ + else if (matched_idx == -1 && + pj_stricmp(&m_rem->desc.transport, &ID_RTP_AVP) == 0) + goto BYPASS_SRTP; + break; + + case PJMEDIA_SRTP_MANDATORY: + /* Do nothing, intentional */ + break; + } + + /* No crypto attr */ + if (!has_crypto_attr) { + DEACTIVATE_MEDIA(sdp_pool, m_loc); + return PJMEDIA_SRTP_ESDPREQCRYPTO; + } + + /* No crypto match */ + if (matched_idx == -1) { + DEACTIVATE_MEDIA(sdp_pool, m_loc); + return PJMEDIA_SRTP_ENOTSUPCRYPTO; + } + + /* we have to generate crypto answer, + * with srtp->tx_policy_neg matched the offer + * and rem_tag contains matched offer tag. + */ buffer_len = MAXLEN; status = generate_crypto_attr_value(srtp->pool, buffer, &buffer_len, - &srtp->setting.crypto[i], - i+1); + &srtp->setting.crypto[matched_idx], + chosen_tag); if (status != PJ_SUCCESS) return status; + srtp->tx_policy_neg = srtp->setting.crypto[matched_idx]; + /* If buffer_len==0, just skip the crypto attribute. */ if (buffer_len) { pj_strset(&attr_value, buffer, buffer_len); - attr = pjmedia_sdp_attr_create(srtp->pool, ID_CRYPTO.ptr, + attr = pjmedia_sdp_attr_create(sdp_pool, ID_CRYPTO.ptr, &attr_value); m_loc->attr[m_loc->attr_count++] = attr; } - } - } else { - /* find supported crypto-suite, get the tag, and assign policy_local */ - pjmedia_srtp_crypto tmp_rx_crypto; - pj_bool_t has_crypto_attr = PJ_FALSE; - pj_bool_t has_match = PJ_FALSE; - int chosen_tag = 0; - int tags[64]; /* assume no more than 64 crypto attrs in a media */ - int cr_attr_count = 0; - int k; - - for (i=0; i<m_rem->attr_count; ++i) { - if (pj_stricmp(&m_rem->attr[i]->name, &ID_CRYPTO) != 0) - continue; - has_crypto_attr = PJ_TRUE; - - status = parse_attr_crypto(srtp->pool, m_rem->attr[i], - &tmp_rx_crypto, &tags[cr_attr_count]); - if (status != PJ_SUCCESS) - return status; - - /* Check duplicated tag */ - for (k=0; k<cr_attr_count; ++k) { - if (tags[k] == tags[cr_attr_count]) { - DEACTIVATE_MEDIA(pool, m_loc); - return PJMEDIA_SRTP_ESDPDUPCRYPTOTAG; - } - } - - if (!has_match) { - /* lets see if the crypto-suite offered is supported */ - for (j=0; j<srtp->setting.crypto_count; ++j) - if (pj_stricmp(&tmp_rx_crypto.name, - &srtp->setting.crypto[j].name) == 0) - { - int cs_idx = get_crypto_idx(&tmp_rx_crypto.name); - - /* Force to use test key */ - /* bad keys for snom: */ - //char *hex_test_key = "58b29c5c8f42308120ce857e439f2d" - // "7810a8b10ad0b1446be5470faea496"; - //char *hex_test_key = "20a26aac7ba062d356ff52b61e3993" - // "ccb78078f12c64db94b9c294927fd0"; - //pj_str_t *test_key = &srtp->setting.crypto[j].key; - //char *raw_test_key = pj_pool_zalloc(srtp->pool, 64); - //hex_string_to_octet_string( - // raw_test_key, - // hex_test_key, - // strlen(hex_test_key)); - //pj_strset(test_key, raw_test_key, - // crypto_suites[cs_idx].cipher_key_len); - /* EO Force to use test key */ - - if (tmp_rx_crypto.key.slen != - (int)crypto_suites[cs_idx].cipher_key_len) - return PJMEDIA_SRTP_EINKEYLEN; - - srtp->tx_policy = srtp->setting.crypto[j]; - srtp->rx_policy = tmp_rx_crypto; - chosen_tag = tags[cr_attr_count]; - has_match = PJ_TRUE; - break; - } - } - cr_attr_count++; - } - - if (srtp->setting.use == PJMEDIA_SRTP_DISABLED) { - /* bypass when remote uses RTP/AVP and we disable SRTP */ - goto BYPASS_SRTP; - } else if (srtp->setting.use == PJMEDIA_SRTP_OPTIONAL) { - /* bypass SRTP when no crypto-attr but remote uses RTP/AVP */ - if (!has_crypto_attr && - pj_stricmp(&m_rem->desc.transport, &ID_RTP_AVP) == 0) - goto BYPASS_SRTP; - /* bypass SRTP when nothing match but remote uses RTP/AVP */ - if (!has_match && - pj_stricmp(&m_rem->desc.transport, &ID_RTP_AVP) == 0) - goto BYPASS_SRTP; - } else if (srtp->setting.use == PJMEDIA_SRTP_MANDATORY) { - /* do nothing, this is intended */ - } - - /* No crypto attr */ - if (!has_crypto_attr) { - DEACTIVATE_MEDIA(pool, m_loc); - return PJMEDIA_SRTP_ESDPREQCRYPTO; - } - - /* No crypto match */ - if (!has_match) { - DEACTIVATE_MEDIA(pool, m_loc); - return PJMEDIA_SRTP_ENOTSUPCRYPTO; + /* At this point, we get valid rx_policy_neg & tx_policy_neg. */ } - - /* we have to generate crypto answer, - * with srtp->tx_policy matched the offer - * and rem_tag contains matched offer tag. - */ - buffer_len = MAXLEN; - status = generate_crypto_attr_value(srtp->pool, buffer, &buffer_len, - &srtp->tx_policy, - chosen_tag); - if (status != PJ_SUCCESS) - return status; - - /* If buffer_len==0, just skip the crypto attribute. */ - if (buffer_len) { - pj_strset(&attr_value, buffer, buffer_len); - attr = pjmedia_sdp_attr_create(srtp->pool, ID_CRYPTO.ptr, - &attr_value); - m_loc->attr[m_loc->attr_count++] = attr; - } - - /* At this point, - * we should have valid rx_policy & tx_policy. - */ + } goto PROPAGATE_MEDIA_CREATE; BYPASS_SRTP: srtp->bypass_srtp = PJ_TRUE; - member_tp_option &= ~PJMEDIA_TPMED_NO_TRANSPORT_CHECKING; PROPAGATE_MEDIA_CREATE: - return pjmedia_transport_media_create(srtp->real_tp, pool, - member_tp_option, - sdp_local, sdp_remote, media_index); + return pjmedia_transport_encode_sdp(srtp->member_tp, sdp_pool, + sdp_local, sdp_remote, media_index); } static pj_status_t transport_media_start(pjmedia_transport *tp, pj_pool_t *pool, - pjmedia_sdp_session *sdp_local, + const pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *sdp_remote, unsigned media_index) { @@ -1196,6 +1283,9 @@ static pj_status_t transport_media_start(pjmedia_transport *tp, m_rem = sdp_remote->media[media_index]; m_loc = sdp_local->media[media_index]; + srtp->remote_prefer_rtp_savp = (pj_stricmp(&m_rem->desc.transport, + &ID_RTP_SAVP) == 0); + /* For answerer side, this function will just have to start SRTP */ /* Check remote media transport & set local media transport @@ -1263,8 +1353,8 @@ static pj_status_t transport_media_start(pjmedia_transport *tp, return PJMEDIA_SRTP_ECRYPTONOTMATCH; } - srtp->tx_policy = srtp->setting.crypto[rem_tag-1]; - srtp->rx_policy = tmp_tx_crypto; + srtp->tx_policy_neg = srtp->setting.crypto[rem_tag-1]; + srtp->rx_policy_neg = tmp_tx_crypto; } if (srtp->setting.use == PJMEDIA_SRTP_DISABLED) { @@ -1280,13 +1370,11 @@ static pj_status_t transport_media_start(pjmedia_transport *tp, } } - /* At this point, - * we should have valid rx_policy & tx_policy. - */ + /* At this point, we get valid rx_policy_neg & tx_policy_neg. */ } /* Got policy_local & policy_remote, let's initalize the SRTP */ - status = pjmedia_transport_srtp_start(tp, &srtp->tx_policy, &srtp->rx_policy); + status = pjmedia_transport_srtp_start(tp, &srtp->tx_policy_neg, &srtp->rx_policy_neg); if (status != PJ_SUCCESS) return status; @@ -1296,7 +1384,7 @@ BYPASS_SRTP: srtp->bypass_srtp = PJ_TRUE; PROPAGATE_MEDIA_START: - return pjmedia_transport_media_start(srtp->real_tp, pool, + return pjmedia_transport_media_start(srtp->member_tp, pool, sdp_local, sdp_remote, media_index); } @@ -1306,7 +1394,7 @@ static pj_status_t transport_media_stop(pjmedia_transport *tp) struct transport_srtp *srtp = (struct transport_srtp*) tp; pj_status_t status; - status = pjmedia_transport_media_stop(srtp->real_tp); + status = pjmedia_transport_media_stop(srtp->member_tp); if (status != PJ_SUCCESS) PJ_LOG(4, (srtp->pool->obj_name, "SRTP failed stop underlying media transport.")); diff --git a/pjmedia/src/pjmedia/transport_udp.c b/pjmedia/src/pjmedia/transport_udp.c index 0804b8bf..4fd6f919 100644 --- a/pjmedia/src/pjmedia/transport_udp.c +++ b/pjmedia/src/pjmedia/transport_udp.c @@ -129,12 +129,16 @@ static pj_status_t transport_send_rtcp2(pjmedia_transport *tp, static pj_status_t transport_media_create(pjmedia_transport *tp, pj_pool_t *pool, unsigned options, - pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *sdp_remote, unsigned media_index); +static pj_status_t transport_encode_sdp(pjmedia_transport *tp, + pj_pool_t *pool, + pjmedia_sdp_session *sdp_local, + const pjmedia_sdp_session *rem_sdp, + unsigned media_index); static pj_status_t transport_media_start (pjmedia_transport *tp, pj_pool_t *pool, - pjmedia_sdp_session *sdp_local, + const pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *sdp_remote, unsigned media_index); static pj_status_t transport_media_stop(pjmedia_transport *tp); @@ -153,6 +157,7 @@ static pjmedia_transport_op transport_udp_op = &transport_send_rtcp, &transport_send_rtcp2, &transport_media_create, + &transport_encode_sdp, &transport_media_start, &transport_media_stop, &transport_simulate_lost, @@ -790,21 +795,34 @@ static pj_status_t transport_send_rtcp2(pjmedia_transport *tp, static pj_status_t transport_media_create(pjmedia_transport *tp, pj_pool_t *pool, unsigned options, - pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *sdp_remote, unsigned media_index) { struct transport_udp *udp = (struct transport_udp*)tp; - PJ_ASSERT_RETURN(tp && pool && sdp_local, PJ_EINVAL); + PJ_ASSERT_RETURN(tp && pool, PJ_EINVAL); udp->media_options = options; + PJ_UNUSED_ARG(sdp_remote); + PJ_UNUSED_ARG(media_index); + + return PJ_SUCCESS; +} + +static pj_status_t transport_encode_sdp(pjmedia_transport *tp, + pj_pool_t *pool, + pjmedia_sdp_session *sdp_local, + const pjmedia_sdp_session *rem_sdp, + unsigned media_index) +{ + struct transport_udp *udp = (struct transport_udp*)tp; + /* Validate media transport */ /* By now, this transport only support RTP/AVP transport */ if ((udp->media_options & PJMEDIA_TPMED_NO_TRANSPORT_CHECKING) == 0) { pjmedia_sdp_media *m_rem, *m_loc; - m_rem = sdp_remote? sdp_remote->media[media_index] : NULL; + m_rem = rem_sdp? rem_sdp->media[media_index] : NULL; m_loc = sdp_local->media[media_index]; if (pj_stricmp(&m_loc->desc.transport, &ID_RTP_AVP) || @@ -820,29 +838,17 @@ static pj_status_t transport_media_create(pjmedia_transport *tp, static pj_status_t transport_media_start(pjmedia_transport *tp, pj_pool_t *pool, - pjmedia_sdp_session *sdp_local, + const pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *sdp_remote, unsigned media_index) { - struct transport_udp *udp = (struct transport_udp*)tp; - PJ_ASSERT_RETURN(tp && pool && sdp_local, PJ_EINVAL); - /* Validate media transport */ - /* By now, this transport only support RTP/AVP transport */ - if ((udp->media_options & PJMEDIA_TPMED_NO_TRANSPORT_CHECKING) == 0) { - pjmedia_sdp_media *m_rem, *m_loc; - - m_rem = sdp_remote->media[media_index]; - m_loc = sdp_local->media[media_index]; - - if (pj_stricmp(&m_loc->desc.transport, &ID_RTP_AVP) || - pj_stricmp(&m_rem->desc.transport, &ID_RTP_AVP)) - { - pjmedia_sdp_media_deactivate(pool, m_loc); - return PJMEDIA_SDP_EINPROTO; - } - } + PJ_UNUSED_ARG(tp); + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(sdp_local); + PJ_UNUSED_ARG(sdp_remote); + PJ_UNUSED_ARG(media_index); return PJ_SUCCESS; } diff --git a/pjsip/include/pjsua-lib/pjsua_internal.h b/pjsip/include/pjsua-lib/pjsua_internal.h index 766d8782..b3aeb11b 100644 --- a/pjsip/include/pjsua-lib/pjsua_internal.h +++ b/pjsip/include/pjsua-lib/pjsua_internal.h @@ -27,6 +27,22 @@ PJ_BEGIN_DECL /** + * Media transport state. + */ +typedef enum pjsua_med_tp_st +{ + /** Not initialized */ + PJSUA_MED_TP_IDLE, + + /** Initialized (media_create() has been called) */ + PJSUA_MED_TP_INIT, + + /** Running (media_start() has been called) */ + PJSUA_MED_TP_RUNNING + +} pjsua_med_tp_st; + +/** * Structure to be attached to invite dialog. * Given a dialog "dlg", application can retrieve this structure * by accessing dlg->mod_data[pjsua.mod.id]. @@ -52,8 +68,9 @@ typedef struct pjsua_call pjsip_evsub *xfer_sub; /**< Xfer server subscription, if this call was triggered by xfer. */ pjmedia_transport *med_tp; /**< Current media transport. */ - pj_status_t med_tp_st; /**< Media transport status. */ + pj_status_t med_tp_ready;/**< Media transport status. */ pjmedia_transport *med_orig; /**< Original media transport */ + pjsua_med_tp_st med_tp_st; /**< Media transport state */ pj_timer_entry refresh_tm;/**< Timer to send re-INVITE. */ pj_timer_entry hangup_tm; /**< Timer to hangup call. */ pj_stun_nat_type rem_nat_type; /**< NAT type of remote endpoint. */ @@ -321,6 +338,8 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata); pj_status_t pjsua_media_channel_init(pjsua_call_id call_id, pjsip_role_e role, int security_level, + pj_pool_t *tmp_pool, + const pjmedia_sdp_session *rem_sdp, int *sip_err_code); pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, pj_pool_t *pool, @@ -328,7 +347,7 @@ pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, pjmedia_sdp_session **p_sdp, int *sip_err_code); pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, - pjmedia_sdp_session *local_sdp, + const pjmedia_sdp_session *local_sdp, const pjmedia_sdp_session *remote_sdp); pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id); diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c index f20fea0f..d01b5f6e 100644 --- a/pjsip/src/pjsua-lib/pjsua_call.c +++ b/pjsip/src/pjsua-lib/pjsua_call.c @@ -212,8 +212,6 @@ PJ_DEF(pj_status_t) pjsua_enum_calls( pjsua_call_id ids[], } -#define LATE_SDP 0 - /* Allocate one call id */ static pjsua_call_id alloc_call_id(void) { @@ -287,6 +285,7 @@ static int get_secure_level(pjsua_acc_id acc_id, const pj_str_t *dst_uri) return 0; } +/* static int call_get_secure_level(pjsua_call *call) { if (call->inv->dlg->secure) @@ -316,6 +315,7 @@ static int call_get_secure_level(pjsua_call *call) return 0; } +*/ /* @@ -449,23 +449,20 @@ PJ_DEF(pj_status_t) pjsua_call_make_call( pjsua_acc_id acc_id, /* Init media channel */ status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAC, - call->secure_level, NULL); + call->secure_level, dlg->pool, + NULL, NULL); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error initializing media channel", status); goto on_error; } - /* Create SDP offer */ -#if LATE_SDP - offer = NULL; -#else - status = pjsua_media_channel_create_sdp(call->index, dlg->pool, NULL, + /* Create offer */ + status = pjsua_media_channel_create_sdp(call->index, dlg->pool, NULL, &offer, NULL); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "pjmedia unable to create SDP", status); + pjsua_perror(THIS_FILE, "Error initializing media channel", status); goto on_error; } -#endif /* Create the INVITE session: */ options |= PJSIP_INV_SUPPORT_100REL; @@ -718,18 +715,6 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) else call->secure_level = 0; - /* Init media channel */ - status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAS, - call->secure_level, &sip_err_code); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing media channel", status); - pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, - sip_err_code, NULL, - NULL, NULL); - PJSUA_UNLOCK(); - return PJ_TRUE; - } - /* Parse SDP from incoming request */ if (rdata->msg_info.msg->body) { status = pjmedia_sdp_parse(rdata->tp_info.pool, @@ -739,9 +724,8 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) const pj_str_t reason = pj_str("Bad SDP"); pjsua_perror(THIS_FILE, "Error parsing SDP in incoming INVITE", status); - pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 400, &reason, - NULL, NULL); - pjsua_media_channel_deinit(call->index); + pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, 400, + &reason, NULL, NULL, NULL); PJSUA_UNLOCK(); return PJ_TRUE; } @@ -761,19 +745,31 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) offer = NULL; } - /* Get media capability from media endpoint: */ + /* Init media channel */ + status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAS, + call->secure_level, + rdata->tp_info.pool, offer, + &sip_err_code); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error initializing media channel", status); + pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, + sip_err_code, NULL, NULL, NULL, NULL); + PJSUA_UNLOCK(); + return PJ_TRUE; + } + + /* Create answer */ status = pjsua_media_channel_create_sdp(call->index, rdata->tp_info.pool, offer, &answer, &sip_err_code); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error creating SDP answer", status); - pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, - sip_err_code, NULL, - NULL, NULL); - pjsua_media_channel_deinit(call->index); + pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, + sip_err_code, NULL, NULL, NULL, NULL); PJSUA_UNLOCK(); return PJ_TRUE; } + /* Verify that we can handle the request. */ options |= PJSIP_INV_SUPPORT_100REL; if (pjsua_var.acc[acc_id].cfg.require_100rel) @@ -794,10 +790,9 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) NULL, NULL); } else { - /* Respond with 500 (Internal Server Error) */ - pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL, - NULL, NULL); + pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, 500, NULL, + NULL, NULL, NULL); } pjsua_media_channel_deinit(call->index); @@ -1474,18 +1469,6 @@ PJ_DEF(pj_status_t) pjsua_call_reinvite( pjsua_call_id call_id, return PJSIP_ESESSIONSTATE; } - /* Update call secure level */ - call->secure_level = call_get_secure_level(call); - - /* Init media channel */ - status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAC, - call->secure_level, NULL); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing media channel", status); - pjsip_dlg_dec_lock(dlg); - return PJSIP_ESESSIONSTATE; - } - /* Create SDP */ PJ_UNUSED_ARG(unhold); PJ_TODO(create_active_inactive_sdp_based_on_unhold_arg); @@ -1547,18 +1530,6 @@ PJ_DEF(pj_status_t) pjsua_call_update( pjsua_call_id call_id, if (status != PJ_SUCCESS) return status; - /* Update call's secure level */ - call->secure_level = call_get_secure_level(call); - - /* Init media channel */ - status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAC, - call->secure_level, NULL); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing media channel", status); - pjsip_dlg_dec_lock(dlg); - return PJSIP_ESESSIONSTATE; - } - /* Create SDP */ status = pjsua_media_channel_create_sdp(call->index, call->inv->pool, NULL, &sdp, NULL); @@ -1569,7 +1540,9 @@ PJ_DEF(pj_status_t) pjsua_call_update( pjsua_call_id call_id, return status; } - /* Create re-INVITE with new offer */ + update_sdp_version(call, sdp); + + /* Create UPDATE with new offer */ status = pjsip_inv_update(call->inv, NULL, sdp, &tdata); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to create UPDATE request", status); @@ -2537,8 +2510,7 @@ static void pjsua_call_on_media_update(pjsip_inv_session *inv, pj_status_t status) { pjsua_call *call; - const pjmedia_sdp_session *c_local; - pjmedia_sdp_session *local_sdp; + const pjmedia_sdp_session *local_sdp; const pjmedia_sdp_session *remote_sdp; PJSUA_LOCK(); @@ -2567,7 +2539,7 @@ static void pjsua_call_on_media_update(pjsip_inv_session *inv, /* Get local and remote SDP */ - status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &c_local); + status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &local_sdp); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to retrieve currently active local SDP", @@ -2576,7 +2548,6 @@ static void pjsua_call_on_media_update(pjsip_inv_session *inv, PJSUA_UNLOCK(); return; } - local_sdp = (pjmedia_sdp_session*) c_local; status = pjmedia_sdp_neg_get_active_remote(call->inv->neg, &remote_sdp); if (status != PJ_SUCCESS) { @@ -2712,18 +2683,6 @@ static void pjsua_call_on_rx_offer(pjsip_inv_session *inv, PJ_LOG(4,(THIS_FILE, "Call %d: received updated media offer", call->index)); - /* Update call's secure level */ - call->secure_level = call_get_secure_level(call); - - /* Init media channel */ - status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAS, - call->secure_level, NULL); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing media channel", status); - PJSUA_UNLOCK(); - return; - } - status = pjsua_media_channel_create_sdp(call->index, call->inv->pool, offer, &answer, NULL); } @@ -2768,18 +2727,6 @@ static void pjsua_call_on_create_offer(pjsip_inv_session *inv, PJ_LOG(4,(THIS_FILE, "Call %d: asked to send a new offer", call->index)); - /* Update call's secure level */ - call->secure_level = call_get_secure_level(call); - - /* Init media channel */ - status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAC, - call->secure_level, NULL); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing media channel", status); - PJSUA_UNLOCK(); - return; - } - status = pjsua_media_channel_create_sdp(call->index, call->inv->pool, NULL, offer, NULL); } diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c index c678341d..a0c992c3 100644 --- a/pjsip/src/pjsua-lib/pjsua_media.c +++ b/pjsip/src/pjsua-lib/pjsua_media.c @@ -652,7 +652,7 @@ static void on_ice_complete(pjmedia_transport *tp, switch (op) { case PJ_ICE_STRANS_OP_INIT: - pjsua_var.calls[id].med_tp_st = result; + pjsua_var.calls[id].med_tp_ready = result; break; case PJ_ICE_STRANS_OP_NEGOTIATION: if (result != PJ_SUCCESS) { @@ -752,7 +752,7 @@ static pj_status_t create_ice_media_transports(void) pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb)); ice_cb.on_ice_complete = &on_ice_complete; pj_ansi_snprintf(name, sizeof(name), "icetp%02d", i); - pjsua_var.calls[i].med_tp_st = PJ_EPENDING; + pjsua_var.calls[i].med_tp_ready = PJ_EPENDING; comp_cnt = 1; if (PJMEDIA_ADVERTISE_RTCP) @@ -769,14 +769,14 @@ static pj_status_t create_ice_media_transports(void) /* Wait until transport is initialized, or time out */ PJSUA_UNLOCK(); - while (pjsua_var.calls[i].med_tp_st == PJ_EPENDING) { + while (pjsua_var.calls[i].med_tp_ready == PJ_EPENDING) { pjsua_handle_events(100); } PJSUA_LOCK(); - if (pjsua_var.calls[i].med_tp_st != PJ_SUCCESS) { + if (pjsua_var.calls[i].med_tp_ready != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error initializing ICE media transport", - pjsua_var.calls[i].med_tp_st); - status = pjsua_var.calls[i].med_tp_st; + pjsua_var.calls[i].med_tp_ready); + status = pjsua_var.calls[i].med_tp_ready; goto on_error; } @@ -847,15 +847,18 @@ PJ_DEF(pj_status_t) pjsua_media_transports_create( pj_status_t pjsua_media_channel_init(pjsua_call_id call_id, pjsip_role_e role, int security_level, + pj_pool_t *tmp_pool, + const pjmedia_sdp_session *rem_sdp, int *sip_err_code) { + enum { MEDIA_IDX = 0 }; pjsua_call *call = &pjsua_var.calls[call_id]; + pj_status_t status; #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) pjsua_acc *acc = &pjsua_var.acc[call->acc_id]; pjmedia_srtp_setting srtp_opt; pjmedia_transport *srtp; - pj_status_t status; #endif PJ_UNUSED_ARG(role); @@ -904,6 +907,16 @@ pj_status_t pjsua_media_channel_init(pjsua_call_id call_id, PJ_UNUSED_ARG(security_level); #endif + /* Create the media transport */ + status = pjmedia_transport_media_create(call->med_tp, tmp_pool, 0, + rem_sdp, MEDIA_IDX); + if (status != PJ_SUCCESS) { + if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE; + pjsua_media_channel_deinit(call_id); + return status; + } + + call->med_tp_st = PJSUA_MED_TP_INIT; return PJ_SUCCESS; } @@ -926,6 +939,18 @@ pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, return PJ_EBUSY; } + /* Create media if it's not created. This could happen when call is + * currently on-hold + */ + if (call->med_tp_st == PJSUA_MED_TP_IDLE) { + pjsip_role_e role; + role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC); + status = pjsua_media_channel_init(call_id, role, call->secure_level, + pool, rem_sdp, sip_status_code); + if (status != PJ_SUCCESS) + return status; + } + /* Get media socket info */ pjmedia_transport_info_init(&tpinfo); pjmedia_transport_get_info(call->med_tp, &tpinfo); @@ -935,7 +960,7 @@ pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, &tpinfo.sock_info, &sdp); if (status != PJ_SUCCESS) { if (sip_status_code) *sip_status_code = 500; - goto on_error; + return status; } /* Add NAT info in the SDP */ @@ -963,20 +988,15 @@ pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, } /* Give the SDP to media transport */ - status = pjmedia_transport_media_create(call->med_tp, pool, 0, - sdp, rem_sdp, MEDIA_IDX); + status = pjmedia_transport_encode_sdp(call->med_tp, pool, sdp, rem_sdp, + MEDIA_IDX); if (status != PJ_SUCCESS) { if (sip_status_code) *sip_status_code = PJSIP_SC_NOT_ACCEPTABLE; - goto on_error; + return status; } *p_sdp = sdp; return PJ_SUCCESS; - -on_error: - pjsua_media_channel_deinit(call_id); - return status; - } @@ -984,9 +1004,6 @@ static void stop_media_session(pjsua_call_id call_id) { pjsua_call *call = &pjsua_var.calls[call_id]; - //see ticket #525 - //pjmedia_transport_media_stop(call->med_tp); - if (call->conf_slot != PJSUA_INVALID_ID) { if (pjsua_var.mconf) { pjsua_conf_remove_port(call->conf_slot); @@ -1016,7 +1033,10 @@ pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id) stop_media_session(call_id); - pjmedia_transport_media_stop(call->med_tp); + if (call->med_tp_st != PJSUA_MED_TP_IDLE) { + pjmedia_transport_media_stop(call->med_tp); + call->med_tp_st = PJSUA_MED_TP_IDLE; + } if (call->med_orig && call->med_tp != call->med_orig) { pjmedia_transport_close(call->med_tp); @@ -1048,7 +1068,7 @@ static void dtmf_callback(pjmedia_stream *strm, void *user_data, pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, - pjmedia_sdp_session *local_sdp, + const pjmedia_sdp_session *local_sdp, const pjmedia_sdp_session *remote_sdp) { unsigned i; @@ -1112,6 +1132,7 @@ pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, /* Shutdown transport's session */ pjmedia_transport_media_stop(call->med_tp); + call->med_tp_st = PJSUA_MED_TP_IDLE; /* No need because we need keepalive? */ @@ -1122,13 +1143,15 @@ pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, } } else { - /* Start media transport */ + /* Start/restart media transport */ status = pjmedia_transport_media_start(call->med_tp, call->inv->pool, local_sdp, remote_sdp, 0); if (status != PJ_SUCCESS) return status; + call->med_tp_st = PJSUA_MED_TP_RUNNING; + /* Override ptime, if this option is specified. */ if (pjsua_var.media_cfg.ptime != 0) { si->param->setting.frm_per_pkt = (pj_uint8_t) |