void get_and_register_SDP_to_cloud(struct ice_trans_s* icetrans, ice_option_t opt, char *usrid) { static char buffer[2048]; int len; if (icetrans->icest == NULL) { PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first")); return; } PJ_LOG(4, (__FUNCTION__, "General info")); PJ_LOG(4, (__FUNCTION__,"---------------")); PJ_LOG(4, (__FUNCTION__,"Component count : %d\n", opt.comp_cnt)); PJ_LOG(4, (__FUNCTION__,"Status : ")); if (pj_ice_strans_sess_is_complete(icetrans->icest)) puts("negotiation complete"); else if (pj_ice_strans_sess_is_running(icetrans->icest)) puts("negotiation is in progress"); else if (pj_ice_strans_has_sess(icetrans->icest)) puts("session ready"); else puts("session not created"); if (!pj_ice_strans_has_sess(icetrans->icest)) { puts("Create the session first to see more info"); return; } printf("Negotiated comp_cnt: %d\n", pj_ice_strans_get_running_comp_cnt(icetrans->icest)); printf("Role : %s\n", pj_ice_strans_get_role(icetrans->icest)==PJ_ICE_SESS_ROLE_CONTROLLED ? "controlled" : "controlling"); len = extract_sdp_to_xml(icetrans, buffer, 2048, opt, usrid); if (len < 0) err_exit("not enough buffer to show ICE status", -len, icetrans); // Register this local SDP to cloud char full_url[256]; //printf("[DEBUG] %s, %d \n", __FUNCTION__, __LINE__ ); strcpy(full_url, gUrl); // plus URL strcpy(&full_url[strlen(full_url)], "/peer/registerPeer"); // plus API http_post_request(full_url, buffer); PJ_LOG(4, (__FUNCTION__,"Local SDP (paste this to remote host):\n" "--------------------------------------\n" "%s\n", buffer)); }
/* * Send application data to remote agent. */ void natclient_send_data(struct ice_trans_s* icetrans, unsigned comp_id, const char *data) { pj_status_t status; if (icetrans->icest == NULL) { PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first")); return; } if (!pj_ice_strans_has_sess(icetrans->icest)) { PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first")); return; } /* if (!pj_ice_strans_sess_is_complete(icetrans->icest)) { PJ_LOG(1,(THIS_FILE, "Error: ICE negotiation has not been started or is in progress")); return; } */ if (comp_id<1||comp_id>pj_ice_strans_get_running_comp_cnt(icetrans->icest)) { PJ_LOG(1,(THIS_FILE, "Error: invalid component ID")); return; } status = pj_ice_strans_sendto(icetrans->icest, comp_id, data, strlen(data), &icetrans->rem.def_addr[comp_id-1], pj_sockaddr_get_len(&icetrans->rem.def_addr[comp_id-1])); if (status != PJ_SUCCESS) natclient_perror("Error sending data", status); else PJ_LOG(3,(THIS_FILE, "Data sent")); }
/* * Start ICE negotiation! This function is invoked from the menu. */ void natclient_start_nego(struct ice_trans_s* icetrans) { pj_str_t rufrag, rpwd; pj_status_t status; if (icetrans->icest == NULL) { PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first")); return; } if (!pj_ice_strans_has_sess(icetrans->icest)) { PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first")); return; } if (icetrans->rem.cand_cnt == 0) { PJ_LOG(1,(THIS_FILE, "Error: No remote info, input remote info first")); return; } PJ_LOG(3,(THIS_FILE, "Starting ICE negotiation..")); status = pj_ice_strans_start_ice(icetrans->icest, pj_cstr(&rufrag, icetrans->rem.ufrag), pj_cstr(&rpwd, icetrans->rem.pwd), icetrans->rem.cand_cnt, icetrans->rem.cand); if (status != PJ_SUCCESS) natclient_perror("Error starting ICE", status); else PJ_LOG(3,(THIS_FILE, "ICE negotiation started")); }
/* * Create ICE session, invoked from the menu. */ void natclient_init_session(struct ice_trans_s* icetrans, unsigned rolechar) { pj_ice_sess_role role = (pj_tolower((pj_uint8_t)rolechar)=='o' ? PJ_ICE_SESS_ROLE_CONTROLLING : PJ_ICE_SESS_ROLE_CONTROLLED); pj_status_t status; if (icetrans->icest == NULL) { PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first")); return; } if (pj_ice_strans_has_sess(icetrans->icest)) { PJ_LOG(1,(THIS_FILE, "Error: Session already created")); return; } status = pj_ice_strans_init_ice(icetrans->icest, role, NULL, NULL); if (status != PJ_SUCCESS) natclient_perror("error creating session", status); else PJ_LOG(3,(THIS_FILE, "ICE session created")); reset_rem_info(icetrans); }
/* 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); }
/* * Stop/destroy ICE session, invoked from the menu. */ void natclient_stop_session(struct ice_trans_s* icetrans) { pj_status_t status; if (icetrans->icest == NULL) { PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first")); return; } if (!pj_ice_strans_has_sess(icetrans->icest)) { PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first")); return; } status = pj_ice_strans_stop_ice(icetrans->icest); if (status != PJ_SUCCESS) natclient_perror("error stopping session", status); else PJ_LOG(3,(THIS_FILE, "ICE session stopped")); reset_rem_info(icetrans); }
/* * Stop/destroy ICE session, invoked from the menu. */ static void icedemo_stop_session(void) { pj_status_t status; if (icedemo.icest == NULL) { PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first")); return; } if (!pj_ice_strans_has_sess(icedemo.icest)) { PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first")); return; } status = pj_ice_strans_stop_ice(icedemo.icest); if (status != PJ_SUCCESS) icedemo_perror("error stopping session", status); else PJ_LOG(3,(THIS_FILE, "ICE session stopped")); reset_rem_info(); }
/* * Show information contained in the ICE stream transport. This is * invoked from the menu. */ static void icedemo_show_ice(void) { static char buffer[1000]; int len; if (icedemo.icest == NULL) { PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first")); return; } puts("General info"); puts("---------------"); printf("Component count : %d\n", icedemo.opt.comp_cnt); printf("Status : "); if (pj_ice_strans_sess_is_complete(icedemo.icest)) puts("negotiation complete"); else if (pj_ice_strans_sess_is_running(icedemo.icest)) puts("negotiation is in progress"); else if (pj_ice_strans_has_sess(icedemo.icest)) puts("session ready"); else puts("session not created"); if (!pj_ice_strans_has_sess(icedemo.icest)) { puts("Create the session first to see more info"); return; } printf("Negotiated comp_cnt: %d\n", pj_ice_strans_get_running_comp_cnt(icedemo.icest)); printf("Role : %s\n", pj_ice_strans_get_role(icedemo.icest)==PJ_ICE_SESS_ROLE_CONTROLLED ? "controlled" : "controlling"); len = encode_session(buffer, sizeof(buffer)); if (len < 0) err_exit("not enough buffer to show ICE status", -len); puts(""); printf("Local SDP (paste this to remote host):\n" "--------------------------------------\n" "%s\n", buffer); puts(""); puts("Remote info:\n" "----------------------"); if (icedemo.rem.cand_cnt==0) { puts("No remote info yet"); } else { unsigned i; printf("Remote ufrag : %s\n", icedemo.rem.ufrag); printf("Remote password : %s\n", icedemo.rem.pwd); printf("Remote cand. cnt. : %d\n", icedemo.rem.cand_cnt); for (i=0; i<icedemo.rem.cand_cnt; ++i) { len = print_cand(buffer, sizeof(buffer), &icedemo.rem.cand[i]); if (len < 0) err_exit("not enough buffer to show ICE status", -len); printf(" %s", buffer); } } }
int krx_ice_start_session(krx_ice* k) { pj_status_t r; if(!k) { return - 1; } if(!k->ice_st) { return -2; } if(pj_ice_strans_has_sess(k->ice_st)) { printf("Error: ice already has a session.\n"); return -3; } r = pj_ice_strans_init_ice(k->ice_st, PJ_ICE_SESS_ROLE_CONTROLLED, NULL, NULL); if(r != PJ_SUCCESS) { printf("Error: cannot initialize an ice session.\n"); return -4; } /* this is where we can create an sdp */ char sdp_buf[8096] = { 0 } ; sprintf(sdp_buf, "v=0\n" "o=- 123456789 34234324 IN IP4 localhost\n" /* - [identifier] [session version] IN IP4 localhost */ "s=krx_ice\n" /* software */ "t=0 0\n" /* start, ending time */ "a=ice-ufrag:%s\n" "a=ice-pwd:%s\n" , k->ice_ufrag, k->ice_pwd ); /* write each component */ for(int i = 0; i < k->ncomp; ++i) { pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND] = { 0 } ; char ipaddr[PJ_INET6_ADDRSTRLEN] = { 0 } ; /* get default candidate for component, note that compoments start numbering from 1, not zero. */ r = pj_ice_strans_get_def_cand(k->ice_st, 1, &cand[0]); if(r != PJ_SUCCESS) { printf("Error: cannot retrieve default candidate for component: %d\n", i+1); continue; } if(i == 0) { int offset = strlen(sdp_buf); sprintf(sdp_buf + offset, "m=video %d RTP/SAVPF 120\n" "c=IN IP4 %s\n" , (int)pj_sockaddr_get_port(&cand[0].addr), pj_sockaddr_print(&cand[0].addr, ipaddr, sizeof(ipaddr), 0) ); /* print all candidates */ unsigned num_cands = PJ_ARRAY_SIZE(cand); printf("Found number of candidates: %d\n", num_cands); // (ice_st && ice_st->ice && comp_id && comp_id <= ice_st->comp_cnt && count && cand), printf("ice: %p\n", k->ice_st); r = pj_ice_strans_enum_cands(k->ice_st, i + 1, &num_cands, cand); if(r != PJ_SUCCESS) { printf("Error: cannot retrieve candidates.\n"); exit(1); } #if 1 for(int j = 0; j < num_cands; ++j) { int offset = strlen(sdp_buf); char* start_addr = sdp_buf + offset; krx_ice_candidate_to_string(sdp_buf, sizeof(sdp_buf)-offset, &cand[j]); char* end_addr = sdp_buf + strlen(sdp_buf); printf("--------\n%s\n--------------\n", sdp_buf); } offset = strlen(sdp_buf); char* start_addr = sdp_buf + offset; krx_ice_candidate_to_string(sdp_buf + offset, sizeof(sdp_buf)-offset, &cand[1]); char* end_addr = sdp_buf + strlen(sdp_buf); #endif } } printf("SDP: %s\n", sdp_buf); r = pj_ice_strans_init_ice(k->ice_st, PJ_ICE_SESS_ROLE_CONTROLLED, NULL, NULL); CHECK_PJ_STATUS(r, "Error: cannot init ice session.\n", -4); return 0; }
/* 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; }
/* 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; }