/* 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; /* 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; /* 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; }
PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_media_find_attr( const pjmedia_sdp_media *m, const pj_str_t *name, const pj_str_t *fmt) { PJ_ASSERT_RETURN(m && name, NULL); return pjmedia_sdp_attr_find(m->attr_count, m->attr, name, fmt); }
void Sdp::setActiveRemoteSdpSession(const pjmedia_sdp_session *sdp) { activeRemoteSession_ = (pjmedia_sdp_session*) sdp; if (!sdp) { ERROR("Remote sdp is NULL while parsing telephone event attribute"); return; } for (unsigned i = 0; i < sdp->media_count; i++) if (pj_stricmp2(&sdp->media[i]->desc.media, "audio") == 0) { pjmedia_sdp_media *r_media = sdp->media[i]; static const pj_str_t STR_TELEPHONE_EVENT = { (char*) "telephone-event", 15}; pjmedia_sdp_attr *attribute = pjmedia_sdp_attr_find(r_media->attr_count, r_media->attr, &STR_TELEPHONE_EVENT, NULL); if (attribute != NULL) { pjmedia_sdp_rtpmap *rtpmap; pjmedia_sdp_attr_to_rtpmap(memPool_, attribute, &rtpmap); telephoneEventPayload_ = pj_strtoul(&rtpmap->pt); } return; } ERROR("Could not found dtmf event from remote sdp"); }
pjmedia_sdp_attr_find2(unsigned count, pjmedia_sdp_attr *const attr_array[], const char *c_name, const pj_str_t *c_fmt) { pj_str_t name; name.ptr = (char*)c_name; name.slen = pj_ansi_strlen(c_name); return pjmedia_sdp_attr_find(count, attr_array, &name, c_fmt); }
/* Compare attributes array. */ static pj_status_t compare_attr_imp(int inst_id, unsigned count1, pjmedia_sdp_attr *const attr1[], unsigned count2, pjmedia_sdp_attr *const attr2[]) { pj_status_t status; unsigned i; const pj_str_t inactive = { "inactive", 8 }; const pj_str_t sendrecv = { "sendrecv", 8 }; const pj_str_t sendonly = { "sendonly", 8 }; const pj_str_t recvonly = { "recvonly", 8 }; const pj_str_t fmtp = { "fmtp", 4 }; const pj_str_t rtpmap = { "rtpmap", 6 }; /* For simplicity, we only compare the following attributes, and ignore * the others: * - direction, eg. inactive, sendonly, recvonly, sendrecv * - fmtp for each payload. * - rtpmap for each payload. */ for (i=0; i<count1; ++i) { const pjmedia_sdp_attr *a1 = attr1[i]; if (pj_strcmp(&a1->name, &inactive) == 0 || pj_strcmp(&a1->name, &sendrecv) == 0 || pj_strcmp(&a1->name, &sendonly) == 0 || pj_strcmp(&a1->name, &recvonly) == 0) { /* For inactive, sendrecv, sendonly, and recvonly attributes, * the same attribute must be present on the other SDP. */ const pjmedia_sdp_attr *a2; a2 = pjmedia_sdp_attr_find(count2, attr2, &a1->name, NULL); if (!a2) return PJMEDIA_SDP_EDIRNOTEQUAL; } else if (pj_strcmp(&a1->name, &fmtp) == 0) { /* For fmtp attribute, find the fmtp attribute in the other SDP * for the same payload type, and compare the fmtp param/value. */ pjmedia_sdp_fmtp fmtp1, fmtp2; const pjmedia_sdp_attr *a2; status = pjmedia_sdp_attr_get_fmtp(a1, &fmtp1); if (status != PJ_SUCCESS) return PJMEDIA_SDP_EFMTPNOTEQUAL; a2 = pjmedia_sdp_attr_find(count2, attr2, &a1->name, &fmtp1.fmt); if (!a2) return PJMEDIA_SDP_EFMTPNOTEQUAL; status = pjmedia_sdp_attr_get_fmtp(a2, &fmtp2); if (status != PJ_SUCCESS) return PJMEDIA_SDP_EFMTPNOTEQUAL; if (pj_strcmp(&fmtp1.fmt_param, &fmtp2.fmt_param) != 0) return PJMEDIA_SDP_EFMTPNOTEQUAL; } else if (pj_strcmp(&a1->name, &rtpmap) == 0) { /* For rtpmap attribute, find rtpmap attribute on the other SDP * for the same payload type, and compare both rtpmap atribute * values. */ pjmedia_sdp_rtpmap r1, r2; const pjmedia_sdp_attr *a2; status = pjmedia_sdp_attr_get_rtpmap(inst_id, a1, &r1); if (status != PJ_SUCCESS) return PJMEDIA_SDP_ERTPMAPNOTEQUAL; a2 = pjmedia_sdp_attr_find(count2, attr2, &a1->name, &r1.pt); if (!a2) return PJMEDIA_SDP_ERTPMAPNOTEQUAL; status = pjmedia_sdp_attr_get_rtpmap(inst_id, a2, &r2); if (status != PJ_SUCCESS) return PJMEDIA_SDP_ERTPMAPNOTEQUAL; if (pj_strcmp(&r1.pt, &r2.pt) != 0) return PJMEDIA_SDP_ERTPMAPNOTEQUAL; if (pj_strcmp(&r1.enc_name, &r2.enc_name) != 0) return PJMEDIA_SDP_ERTPMAPNOTEQUAL; if (r1.clock_rate != r2.clock_rate) return PJMEDIA_SDP_ERTPMAPNOTEQUAL; if (pj_strcmp(&r1.param, &r2.param) != 0) return PJMEDIA_SDP_ERTPMAPNOTEQUAL; } } 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 *ufrag_attr, *pwd_attr; const pjmedia_sdp_conn *rem_conn; pj_bool_t comp1_found=PJ_FALSE, comp2_found=PJ_FALSE, has_rtcp=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 * candidate for 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; if (tp_ice->comp_cnt > 1) { const pjmedia_sdp_attr *attr; /* Get default RTCP candidate from a=rtcp line, if present, otherwise * calculate default RTCP candidate from default RTP target. */ attr = pjmedia_sdp_attr_find(rem_m->attr_count, rem_m->attr, &STR_RTCP, NULL); has_rtcp = (attr != NULL); if (attr) { 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; } if (rtcp_attr.addr.slen) { /* 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; } } else { /* Assign RTCP address */ status = pj_sockaddr_init(tp_ice->af, &rtcp_addr, NULL, (pj_uint16_t)rtcp_attr.port); if (status != PJ_SUCCESS) { return PJMEDIA_SDP_EINRTCP; } pj_sockaddr_copy_addr(&rtcp_addr, &rem_conn_addr); } } else { unsigned rtcp_port; rtcp_port = pj_sockaddr_get_port(&rem_conn_addr) + 1; pj_sockaddr_cp(&rtcp_addr, &rem_conn_addr); pj_sockaddr_set_port(&rtcp_addr, (pj_uint16_t)rtcp_port); } } /* Find the default addresses 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) { comp1_found = PJ_TRUE; } else if (!comp2_found && cand.comp_id==COMP_RTCP && pj_sockaddr_cmp(&rtcp_addr, &cand.addr)==0) { comp2_found = PJ_TRUE; } if (cand.comp_id == COMP_RTCP) has_rtcp = PJ_TRUE; if (comp1_found && (comp2_found || tp_ice->comp_cnt==1)) break; } /* Check matched component count and ice_mismatch */ if (comp1_found && (tp_ice->comp_cnt==1 || !has_rtcp)) { sdp_state->match_comp_cnt = 1; sdp_state->ice_mismatch = PJ_FALSE; } else if (comp1_found && comp2_found) { sdp_state->match_comp_cnt = 2; sdp_state->ice_mismatch = PJ_FALSE; } else { sdp_state->match_comp_cnt = (tp_ice->comp_cnt > 1 && has_rtcp)? 2 : 1; sdp_state->ice_mismatch = PJ_TRUE; } /* 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, "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; }
/* 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; /* 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; } /* 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); 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); } /* 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; } 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; } } /* 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); } /* Create buffer to encode candidates as SDP attribute */ attr_buf = (char*) pj_pool_alloc(sdp_pool, ATTR_BUF_LEN); 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, &cand_cnt, cand); if (status != PJ_SUCCESS) return status; for (i=0; i<cand_cnt; ++i) { pj_str_t value; value.slen = print_sdp_cand_attr(attr_buf, ATTR_BUF_LEN, &cand[i]); if (value.slen < 0) { pj_assert(!"Not enough attr_buf to print candidate"); return PJ_EBUG; } 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 */ } /* Removing a=rtcp line when there is only one component. */ if (comp_cnt == 1) { attr = pjmedia_sdp_attr_find(m->attr_count, m->attr, &STR_RTCP, NULL); if (attr) pjmedia_sdp_attr_remove(&m->attr_count, m->attr, attr); } return PJ_SUCCESS; }
/* * 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) { /* Something wrong in the SDP answer */ set_no_ice(tp_ice, "Invalid remote SDP answer", status); return status; } /* 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) { /* Remote has reported ice-mismatch */ set_no_ice(tp_ice, "Remote answer contains 'ice-mismatch' attribute", PJ_SUCCESS); return PJ_SUCCESS; } /* 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.")); } /* 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")); } /* 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 */ } /* 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; } /* Done */ tp_ice->use_ice = PJ_TRUE; return PJ_SUCCESS; }