ice-agent.cpp 30.3 KB
Newer Older
1 2
/*
 * ice-agent.cpp
Ronan's avatar
Ronan committed
3
 * Copyright (C) 2010-2018 Belledonne Communications SARL
4
 *
Ghislain MARY's avatar
Ghislain MARY committed
5 6 7 8
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
9 10 11 12 13 14 15
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
Ghislain MARY's avatar
Ghislain MARY committed
16 17
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 19
 */

Ronan's avatar
Ronan committed
20
#include "linphone/core.h"
21

22 23
#include "private.h"

Ronan's avatar
Ronan committed
24
#include "conference/session/media-session-p.h"
25
#include "core/core.h"
26 27
#include "logger/logger.h"

Ronan's avatar
Ronan committed
28
#include "ice-agent.h"
29

Ronan's avatar
Ronan committed
30
// =============================================================================
31

32 33 34 35 36 37 38
using namespace std;

LINPHONE_BEGIN_NAMESPACE

bool IceAgent::candidatesGathered () const {
	if (!iceSession)
		return false;
39
	return !!ice_session_candidates_gathered(iceSession);
40 41 42
}

void IceAgent::checkSession (IceRole role, bool isReinvite) {
43
	// Already created.
44
	if (iceSession)
45 46
		return;

47
	LinphoneConfig *config = linphone_core_get_config(mediaSession.getCore()->getCCore());
48 49 50 51 52 53
	
	if (lp_config_get_int(config, "net", "force_ice_disablement", 0)){
		lWarning()<<"ICE is disabled in this version";
		return;
	}
	
54 55
	if (isReinvite && (lp_config_get_int(config, "net", "allow_late_ice", 0) == 0))
		return;
56

57
	iceSession = ice_session_new();
58 59 60 61 62 63

	// For backward compatibility purposes, shall be enabled by default in the future.
	ice_session_enable_message_integrity_check(
		iceSession,
		!!lp_config_get_int(config, "net", "ice_session_enable_message_integrity_check", 1)
	);
64 65 66 67 68 69 70 71 72 73 74 75 76
	if (lp_config_get_int(config, "net", "dont_default_to_stun_candidates", 0)) {
		IceCandidateType types[ICT_CandidateTypeMax];
		types[0] = ICT_HostCandidate;
		types[1] = ICT_RelayedCandidate;
		types[2] = ICT_CandidateInvalid;
		ice_session_set_default_candidates_types(iceSession, types);
	}
	ice_session_set_role(iceSession, role);
}

void IceAgent::deleteSession () {
	if (!iceSession)
		return;
77

78 79 80 81 82 83
	ice_session_destroy(iceSession);
	iceSession = nullptr;
	mediaSession.getPrivate()->deactivateIce();
}

void IceAgent::gatheringFinished () {
84
	const SalMediaDescription *rmd = mediaSession.getPrivate()->getOp()->getRemoteMediaDescription();
85 86 87 88
	if (rmd)
		clearUnusedIceCandidates(mediaSession.getPrivate()->getLocalDesc(), rmd);
	if (!iceSession)
		return;
89

90 91 92
	ice_session_compute_candidates_foundations(iceSession);
	ice_session_eliminate_redundant_candidates(iceSession);
	ice_session_choose_default_candidates(iceSession);
93

94
	int pingTime = ice_session_average_gathering_round_trip_time(iceSession);
95
	if (pingTime >= 0) {
96 97 98 99 100 101 102 103 104 105 106 107
		mediaSession.getPrivate()->setPingTime(pingTime);
	}
}

int IceAgent::getNbLosingPairs () const {
	if (!iceSession)
		return 0;
	return ice_session_nb_losing_pairs(iceSession);
}

bool IceAgent::hasCompleted () const {
	if (!iceSession)
Ghislain MARY's avatar
Ghislain MARY committed
108
		return true;
109
	return ice_session_state(iceSession) == IS_Completed;
110 111 112 113 114 115 116 117
}

bool IceAgent::hasCompletedCheckList () const {
	if (!iceSession)
		return false;
	switch (ice_session_state(iceSession)) {
		case IS_Completed:
		case IS_Failed:
118
			return !!ice_session_has_completed_check_list(iceSession);
119 120 121 122 123 124 125 126
		default:
			return false;
	}
}

bool IceAgent::isControlling () const {
	if (!iceSession)
		return false;
127
	return ice_session_role(iceSession) == IR_Controlling;
128 129
}

130
bool IceAgent::prepare (const SalMediaDescription *localDesc, bool incomingOffer, bool allowGathering) {
131 132 133 134 135 136
	if (!iceSession)
		return false;

	SalMediaDescription *remoteDesc = nullptr;
	bool hasVideo = false;
	if (incomingOffer) {
137
		remoteDesc = mediaSession.getPrivate()->getOp()->getRemoteMediaDescription();
138
		hasVideo = linphone_core_video_enabled(mediaSession.getCore()->getCCore()) &&
139
			linphone_core_media_description_contains_video_stream(remoteDesc);
140 141 142 143 144 145 146 147
	} else
		hasVideo = mediaSession.getMediaParams()->videoEnabled();

	prepareIceForStream(mediaSession.getPrivate()->getMediaStream(LinphoneStreamTypeAudio), true);
	if (hasVideo)
		prepareIceForStream(mediaSession.getPrivate()->getMediaStream(LinphoneStreamTypeVideo), true);
	if (mediaSession.getMediaParams()->realtimeTextEnabled())
		prepareIceForStream(mediaSession.getPrivate()->getMediaStream(LinphoneStreamTypeText), true);
148 149

	// Start ICE gathering.
150
	if (incomingOffer){
151 152
		// This may delete the ice session.
		updateFromRemoteMediaDescription(localDesc, remoteDesc, true);
153 154
	}
	if (iceSession && allowGathering && !ice_session_candidates_gathered(iceSession)) {
155 156 157
		mediaSession.getPrivate()->prepareStreamsForIceGathering(hasVideo);
		int err = gatherIceCandidates();
		if (err == 0) {
158
			// Ice candidates gathering wasn't started, but we can proceed with the call anyway.
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
			mediaSession.getPrivate()->stopStreamsForIceGathering();
			return false;
		} else if (err == -1) {
			mediaSession.getPrivate()->stopStreamsForIceGathering();
			deleteSession();
			return false;
		}
		return true;
	}
	return false;
}

void IceAgent::prepareIceForStream (MediaStream *ms, bool createChecklist) {
	if (!iceSession)
		return;
174

175 176 177 178 179
	int streamIndex = mediaSession.getPrivate()->getStreamIndex(ms);
	rtp_session_set_pktinfo(ms->sessions.rtp_session, true);
	IceCheckList *cl = ice_session_check_list(iceSession, streamIndex);
	if (!cl && createChecklist) {
		cl = ice_check_list_new();
Benjamin REIS's avatar
Benjamin REIS committed
180
		ice_session_add_check_list(iceSession, cl, static_cast<unsigned int>(streamIndex));
181 182 183 184 185 186
		lInfo() << "Created new ICE check list for stream [" << streamIndex << "]";
	}
	if (cl)
		media_stream_set_ice_check_list(ms, cl);
}

187 188 189 190 191 192
void IceAgent::resetSession (IceRole role) {
	if (!iceSession)
		return;
	ice_session_reset(iceSession, role);
}

193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
void IceAgent::restartSession (IceRole role) {
	if (!iceSession)
		return;
	ice_session_restart(iceSession, role);
}

void IceAgent::startConnectivityChecks () {
	if (!iceSession)
		return;
	ice_session_start_connectivity_checks(iceSession);
}

void IceAgent::stopIceForInactiveStreams (SalMediaDescription *desc) {
	if (!iceSession)
		return;
	if (ice_session_state(iceSession) == IS_Completed)
		return;
	for (int i = 0; i < desc->nb_streams; i++) {
		IceCheckList *cl = ice_session_check_list(iceSession, i);
		if (!sal_stream_description_active(&desc->streams[i]) && cl) {
			ice_session_remove_check_list(iceSession, cl);
			mediaSession.getPrivate()->clearIceCheckList(cl);
		}
	}
	updateIceStateInCallStats();
}

220 221 222 223 224
void IceAgent::updateFromRemoteMediaDescription (
	const SalMediaDescription *localDesc,
	const SalMediaDescription *remoteDesc,
	bool isOffer
) {
225 226
	if (!iceSession)
		return;
227

228
	if (!iceParamsFoundInRemoteMediaDescription(remoteDesc)) {
229
		// Response from remote does not contain mandatory ICE attributes, delete the session.
230
		deleteSession();
231
		mediaSession.getPrivate()->enableSymmetricRtp(!!linphone_core_symmetric_rtp_enabled(mediaSession.getCore()->getCCore()));
232 233 234
		return;
	}

235
	// Check for ICE restart and set remote credentials.
236 237
	bool iceRestarted = checkForIceRestartAndSetRemoteCredentials(remoteDesc, isOffer);

238
	// Create ICE check lists if needed and parse ICE attributes.
239 240 241 242 243 244
	createIceCheckListsAndParseIceAttributes(remoteDesc, iceRestarted);
	for (int i = 0; i < remoteDesc->nb_streams; i++) {
		const SalStreamDescription *stream = &remoteDesc->streams[i];
		IceCheckList *cl = ice_session_check_list(iceSession, i);
		if (!cl) continue;
		if (!sal_stream_description_active(stream)) {
Benjamin REIS's avatar
Benjamin REIS committed
245
			ice_session_remove_check_list_from_idx(iceSession, static_cast<unsigned int>(i));
246 247 248 249 250 251 252 253
			mediaSession.getPrivate()->clearIceCheckList(cl);
		}
	}
	clearUnusedIceCandidates(localDesc, remoteDesc);
	ice_session_check_mismatch(iceSession);

	if (ice_session_nb_check_lists(iceSession) == 0) {
		deleteSession();
254
		mediaSession.getPrivate()->enableSymmetricRtp(!!linphone_core_symmetric_rtp_enabled(mediaSession.getCore()->getCCore()));
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
	}
}

void IceAgent::updateIceStateInCallStats () {
	if (!iceSession)
		return;
	IceCheckList *audioCheckList = ice_session_check_list(iceSession, mediaSession.getPrivate()->getStreamIndex(LinphoneStreamTypeAudio));
	IceCheckList *videoCheckList = ice_session_check_list(iceSession, mediaSession.getPrivate()->getStreamIndex(LinphoneStreamTypeVideo));
	IceCheckList *textCheckList = ice_session_check_list(iceSession, mediaSession.getPrivate()->getStreamIndex(LinphoneStreamTypeText));
	if (!audioCheckList && !videoCheckList && !textCheckList)
		return;

	LinphoneCallStats *audioStats = mediaSession.getPrivate()->getStats(LinphoneStreamTypeAudio);
	LinphoneCallStats *videoStats = mediaSession.getPrivate()->getStats(LinphoneStreamTypeVideo);
	LinphoneCallStats *textStats = mediaSession.getPrivate()->getStats(LinphoneStreamTypeText);
	IceSessionState sessionState = ice_session_state(iceSession);
	if ((sessionState == IS_Completed) || ((sessionState == IS_Failed) && ice_session_has_completed_check_list(iceSession))) {
272
		_linphone_call_stats_set_ice_state(audioStats, LinphoneIceStateNotActivated);
273 274 275
		if (audioCheckList && mediaSession.getMediaParams()->audioEnabled())
			updateIceStateInCallStatsForStream(audioStats, audioCheckList);

276
		_linphone_call_stats_set_ice_state(videoStats, LinphoneIceStateNotActivated);
277 278 279
		if (videoCheckList && mediaSession.getMediaParams()->videoEnabled())
			updateIceStateInCallStatsForStream(videoStats, videoCheckList);

280
		_linphone_call_stats_set_ice_state(textStats, LinphoneIceStateNotActivated);
281 282 283 284
		if (textCheckList && mediaSession.getMediaParams()->realtimeTextEnabled())
			updateIceStateInCallStatsForStream(textStats, textCheckList);
	} else if (sessionState == IS_Running) {
		if (audioCheckList && mediaSession.getMediaParams()->audioEnabled())
285
			_linphone_call_stats_set_ice_state(audioStats, LinphoneIceStateInProgress);
286
		if (videoCheckList && mediaSession.getMediaParams()->videoEnabled())
287
			_linphone_call_stats_set_ice_state(videoStats, LinphoneIceStateInProgress);
288
		if (textCheckList && mediaSession.getMediaParams()->realtimeTextEnabled())
289
			_linphone_call_stats_set_ice_state(textStats, LinphoneIceStateInProgress);
290 291
	} else {
		if (audioCheckList && mediaSession.getMediaParams()->audioEnabled())
292
			_linphone_call_stats_set_ice_state(audioStats, LinphoneIceStateFailed);
293
		if (videoCheckList && mediaSession.getMediaParams()->videoEnabled())
294
			_linphone_call_stats_set_ice_state(videoStats, LinphoneIceStateFailed);
295
		if (textCheckList && mediaSession.getMediaParams()->realtimeTextEnabled())
296
			_linphone_call_stats_set_ice_state(textStats, LinphoneIceStateFailed);
297
	}
298 299 300
	lInfo() << "CallSession [" << &mediaSession << "] New ICE state: audio: [" << linphone_ice_state_to_string(linphone_call_stats_get_ice_state(audioStats)) <<
		"]    video: [" << linphone_ice_state_to_string(linphone_call_stats_get_ice_state(videoStats)) <<
		"]    text: [" << linphone_ice_state_to_string(linphone_call_stats_get_ice_state(textStats)) << "]";
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
}

void IceAgent::updateLocalMediaDescriptionFromIce (SalMediaDescription *desc) {
	if (!iceSession)
		return;
	IceCandidate *rtpCandidate = nullptr;
	IceCandidate *rtcpCandidate = nullptr;
	bool result = false;
	IceSessionState sessionState = ice_session_state(iceSession);
	if (sessionState == IS_Completed) {
		IceCheckList *firstCl = nullptr;
		for (int i = 0; i < desc->nb_streams; i++) {
			IceCheckList *cl = ice_session_check_list(iceSession, i);
			if (cl) {
				firstCl = cl;
				break;
			}
		}
		if (firstCl)
320
			result = !!ice_check_list_selected_valid_local_candidate(firstCl, &rtpCandidate, nullptr);
321
		if (result) {
322
			strncpy(desc->addr, rtpCandidate->taddr.ip, sizeof(desc->addr));
323
		} else {
324
			lWarning() << "If ICE has completed successfully, rtp_candidate should be set!";
325 326
			ice_dump_valid_list(firstCl);
		}
327 328 329 330 331 332 333 334 335 336 337
	}

	strncpy(desc->ice_pwd, ice_session_local_pwd(iceSession), sizeof(desc->ice_pwd));
	strncpy(desc->ice_ufrag, ice_session_local_ufrag(iceSession), sizeof(desc->ice_ufrag));
	for (int i = 0; i < desc->nb_streams; i++) {
		SalStreamDescription *stream = &desc->streams[i];
		IceCheckList *cl = ice_session_check_list(iceSession, i);
		rtpCandidate = rtcpCandidate = nullptr;
		if (!sal_stream_description_active(stream) || !cl)
			continue;
		if (ice_check_list_state(cl) == ICL_Completed) {
338
			LinphoneConfig *config = linphone_core_get_config(mediaSession.getCore()->getCCore());
339
			// TODO: Remove `ice_uses_nortpproxy` option, let's say in December 2018.
340
			bool useNoRtpProxy = !!lp_config_get_int(config, "sip", "ice_uses_nortpproxy", false);
341 342
			if (useNoRtpProxy)
				stream->set_nortpproxy = true;
343
			result = !!ice_check_list_selected_valid_local_candidate(ice_session_check_list(iceSession, i), &rtpCandidate, &rtcpCandidate);
344 345
		} else {
			stream->set_nortpproxy = false;
346
			result = !!ice_check_list_default_local_candidate(ice_session_check_list(iceSession, i), &rtpCandidate, &rtcpCandidate);
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383
		}
		if (result) {
			strncpy(stream->rtp_addr, rtpCandidate->taddr.ip, sizeof(stream->rtp_addr));
			strncpy(stream->rtcp_addr, rtcpCandidate->taddr.ip, sizeof(stream->rtcp_addr));
			stream->rtp_port = rtpCandidate->taddr.port;
			stream->rtcp_port = rtcpCandidate->taddr.port;
		} else {
			memset(stream->rtp_addr, 0, sizeof(stream->rtp_addr));
			memset(stream->rtcp_addr, 0, sizeof(stream->rtcp_addr));
		}
		if ((strlen(ice_check_list_local_pwd(cl)) != strlen(desc->ice_pwd)) || (strcmp(ice_check_list_local_pwd(cl), desc->ice_pwd)))
			strncpy(stream->ice_pwd, ice_check_list_local_pwd(cl), sizeof(stream->ice_pwd));
		else
			memset(stream->ice_pwd, 0, sizeof(stream->ice_pwd));
		if ((strlen(ice_check_list_local_ufrag(cl)) != strlen(desc->ice_ufrag)) || (strcmp(ice_check_list_local_ufrag(cl), desc->ice_ufrag)))
			strncpy(stream->ice_ufrag, ice_check_list_local_ufrag(cl), sizeof(stream->ice_ufrag));
		else
			memset(stream->ice_pwd, 0, sizeof(stream->ice_pwd));
		stream->ice_mismatch = ice_check_list_is_mismatch(cl);
		if ((ice_check_list_state(cl) == ICL_Running) || (ice_check_list_state(cl) == ICL_Completed)) {
			memset(stream->ice_candidates, 0, sizeof(stream->ice_candidates));
			int nbCandidates = 0;
			for (int j = 0; j < MIN((int)bctbx_list_size(cl->local_candidates), SAL_MEDIA_DESCRIPTION_MAX_ICE_CANDIDATES); j++) {
				SalIceCandidate *salCandidate = &stream->ice_candidates[nbCandidates];
				IceCandidate *iceCandidate = reinterpret_cast<IceCandidate *>(bctbx_list_nth_data(cl->local_candidates, j));
				const char *defaultAddr = nullptr;
				int defaultPort = 0;
				if (iceCandidate->componentID == 1) {
					defaultAddr = stream->rtp_addr;
					defaultPort = stream->rtp_port;
				} else if (iceCandidate->componentID == 2) {
					defaultAddr = stream->rtcp_addr;
					defaultPort = stream->rtcp_port;
				} else
					continue;
				if (defaultAddr[0] == '\0')
					defaultAddr = desc->addr;
384 385 386 387 388
				// Only include the candidates matching the default destination for each component of the stream if the state is Completed as specified in RFC5245 section 9.1.2.2.
				if (
					ice_check_list_state(cl) == ICL_Completed &&
					!((iceCandidate->taddr.port == defaultPort) && (strlen(iceCandidate->taddr.ip) == strlen(defaultAddr)) && (strcmp(iceCandidate->taddr.ip, defaultAddr) == 0))
				)
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
					continue;
				strncpy(salCandidate->foundation, iceCandidate->foundation, sizeof(salCandidate->foundation));
				salCandidate->componentID = iceCandidate->componentID;
				salCandidate->priority = iceCandidate->priority;
				strncpy(salCandidate->type, ice_candidate_type(iceCandidate), sizeof(salCandidate->type));
				strncpy(salCandidate->addr, iceCandidate->taddr.ip, sizeof(salCandidate->addr));
				salCandidate->port = iceCandidate->taddr.port;
				if (iceCandidate->base && (iceCandidate->base != iceCandidate)) {
					strncpy(salCandidate->raddr, iceCandidate->base->taddr.ip, sizeof(salCandidate->raddr));
					salCandidate->rport = iceCandidate->base->taddr.port;
				}
				nbCandidates++;
			}
		}
		if ((ice_check_list_state(cl) == ICL_Completed) && (ice_session_role(iceSession) == IR_Controlling)) {
			memset(stream->ice_remote_candidates, 0, sizeof(stream->ice_remote_candidates));
			if (ice_check_list_selected_valid_remote_candidate(cl, &rtpCandidate, &rtcpCandidate)) {
				strncpy(stream->ice_remote_candidates[0].addr, rtpCandidate->taddr.ip, sizeof(stream->ice_remote_candidates[0].addr));
				stream->ice_remote_candidates[0].port = rtpCandidate->taddr.port;
				strncpy(stream->ice_remote_candidates[1].addr, rtcpCandidate->taddr.ip, sizeof(stream->ice_remote_candidates[1].addr));
				stream->ice_remote_candidates[1].port = rtcpCandidate->taddr.port;
			} else
				lError() << "ice: Selected valid remote candidates should be present if the check list is in the Completed state";
		} else {
			for (int j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_ICE_REMOTE_CANDIDATES; j++) {
				stream->ice_remote_candidates[j].addr[0] = '\0';
				stream->ice_remote_candidates[j].port = 0;
			}
		}
	}
}

// -----------------------------------------------------------------------------

void IceAgent::addLocalIceCandidates (int family, const char *addr, IceCheckList *audioCl, IceCheckList *videoCl, IceCheckList *textCl) {
	if ((ice_check_list_state(audioCl) != ICL_Completed) && !ice_check_list_candidates_gathered(audioCl)) {
		int rtpPort = mediaSession.getPrivate()->getRtpPort(LinphoneStreamTypeAudio);
		int rtcpPort = mediaSession.getPrivate()->getRtcpPort(LinphoneStreamTypeAudio);
		ice_add_local_candidate(audioCl, "host", family, addr, rtpPort, 1, nullptr);
		ice_add_local_candidate(audioCl, "host", family, addr, rtcpPort, 2, nullptr);
		LinphoneCallStats *audioStats = mediaSession.getPrivate()->getStats(LinphoneStreamTypeAudio);
430
		_linphone_call_stats_set_ice_state(audioStats, LinphoneIceStateInProgress);
431
	}
432
	LinphoneCore *core = mediaSession.getCore()->getCCore();
433 434 435 436 437 438
	if (linphone_core_video_enabled(core) && videoCl && (ice_check_list_state(videoCl) != ICL_Completed) && !ice_check_list_candidates_gathered(videoCl)) {
		int rtpPort = mediaSession.getPrivate()->getRtpPort(LinphoneStreamTypeVideo);
		int rtcpPort = mediaSession.getPrivate()->getRtcpPort(LinphoneStreamTypeVideo);
		ice_add_local_candidate(videoCl, "host", family, addr, rtpPort, 1, nullptr);
		ice_add_local_candidate(videoCl, "host", family, addr, rtcpPort, 2, nullptr);
		LinphoneCallStats *videoStats = mediaSession.getPrivate()->getStats(LinphoneStreamTypeVideo);
439
		_linphone_call_stats_set_ice_state(videoStats, LinphoneIceStateInProgress);
440 441 442 443 444 445 446
	}
	if (mediaSession.getMediaParams()->realtimeTextEnabled() && textCl && (ice_check_list_state(textCl) != ICL_Completed) && !ice_check_list_candidates_gathered(textCl)) {
		int rtpPort = mediaSession.getPrivate()->getRtpPort(LinphoneStreamTypeText);
		int rtcpPort = mediaSession.getPrivate()->getRtcpPort(LinphoneStreamTypeText);
		ice_add_local_candidate(textCl, "host", family, addr, rtpPort, 1, nullptr);
		ice_add_local_candidate(textCl, "host", family, addr, rtcpPort, 2, nullptr);
		LinphoneCallStats *textStats = mediaSession.getPrivate()->getStats(LinphoneStreamTypeText);
447
		_linphone_call_stats_set_ice_state(textStats, LinphoneIceStateInProgress);
448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483
	}
}

bool IceAgent::checkForIceRestartAndSetRemoteCredentials (const SalMediaDescription *md, bool isOffer) {
	bool iceRestarted = false;
	string addr = md->addr;
	if ((addr == "0.0.0.0") || (addr == "::0")) {
		ice_session_restart(iceSession, isOffer ? IR_Controlled : IR_Controlling);
		iceRestarted = true;
	} else {
		for (int i = 0; i < md->nb_streams; i++) {
			const SalStreamDescription *stream = &md->streams[i];
			IceCheckList *cl = ice_session_check_list(iceSession, i);
			string rtpAddr = stream->rtp_addr;
			if (cl && (rtpAddr == "0.0.0.0")) {
				ice_session_restart(iceSession, isOffer ? IR_Controlled : IR_Controlling);
				iceRestarted = true;
				break;
			}
		}
	}
	if (!ice_session_remote_ufrag(iceSession) && !ice_session_remote_pwd(iceSession)) {
		ice_session_set_remote_credentials(iceSession, md->ice_ufrag, md->ice_pwd);
	} else if (ice_session_remote_credentials_changed(iceSession, md->ice_ufrag, md->ice_pwd)) {
		if (!iceRestarted) {
			ice_session_restart(iceSession, isOffer ? IR_Controlled : IR_Controlling);
			iceRestarted = true;
		}
		ice_session_set_remote_credentials(iceSession, md->ice_ufrag, md->ice_pwd);
	}
	for (int i = 0; i < md->nb_streams; i++) {
		const SalStreamDescription *stream = &md->streams[i];
		IceCheckList *cl = ice_session_check_list(iceSession, i);
		if (cl && (stream->ice_pwd[0] != '\0') && (stream->ice_ufrag[0] != '\0')) {
			if (ice_check_list_remote_credentials_changed(cl, stream->ice_ufrag, stream->ice_pwd)) {
				if (!iceRestarted && ice_check_list_get_remote_ufrag(cl) && ice_check_list_get_remote_pwd(cl)) {
484
					// Restart only if remote ufrag/paswd was already set.
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536
					ice_session_restart(iceSession, isOffer ? IR_Controlled : IR_Controlling);
					iceRestarted = true;
				}
				ice_check_list_set_remote_credentials(cl, stream->ice_ufrag, stream->ice_pwd);
				break;
			}
		}
	}
	return iceRestarted;
}

void IceAgent::clearUnusedIceCandidates (const SalMediaDescription *localDesc, const SalMediaDescription *remoteDesc) {
	if (!localDesc)
		return;
	for (int i = 0; i < remoteDesc->nb_streams; i++) {
		const SalStreamDescription *localStream = &localDesc->streams[i];
		const SalStreamDescription *stream = &remoteDesc->streams[i];
		IceCheckList *cl = ice_session_check_list(iceSession, i);
		if (!cl || !localStream)
			continue;
		if (stream->rtcp_mux && localStream->rtcp_mux) {
			ice_check_list_remove_rtcp_candidates(cl);
		}
	}
}

void IceAgent::createIceCheckListsAndParseIceAttributes (const SalMediaDescription *md, bool iceRestarted) {
	for (int i = 0; i < md->nb_streams; i++) {
		const SalStreamDescription *stream = &md->streams[i];
		IceCheckList *cl = ice_session_check_list(iceSession, i);
		if (!cl)
			continue;
		if (stream->ice_mismatch) {
			ice_check_list_set_state(cl, ICL_Failed);
			continue;
		}
		if (stream->rtp_port == 0) {
			ice_session_remove_check_list(iceSession, cl);
			mediaSession.getPrivate()->clearIceCheckList(cl);
			continue;
		}
		if ((stream->ice_pwd[0] != '\0') && (stream->ice_ufrag[0] != '\0'))
			ice_check_list_set_remote_credentials(cl, stream->ice_ufrag, stream->ice_pwd);
		for (int j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_ICE_CANDIDATES; j++) {
			bool defaultCandidate = false;
			const SalIceCandidate *candidate = &stream->ice_candidates[j];
			if (candidate->addr[0] == '\0')
				break;
			if ((candidate->componentID == 0) || (candidate->componentID > 2))
				continue;
			const char *addr = nullptr;
			int port = 0;
Ronan's avatar
Ronan committed
537
			getIceDefaultAddrAndPort(static_cast<uint16_t>(candidate->componentID), md, stream, &addr, &port);
538 539 540 541 542
			if (addr && (candidate->port == port) && (strlen(candidate->addr) == strlen(addr)) && (strcmp(candidate->addr, addr) == 0))
				defaultCandidate = true;
			int family = AF_INET;
			if (strchr(candidate->addr, ':'))
				family = AF_INET6;
Ronan's avatar
Ronan committed
543 544 545 546 547
			ice_add_remote_candidate(
				cl, candidate->type, family, candidate->addr, candidate->port,
				static_cast<uint16_t>(candidate->componentID),
				candidate->priority, candidate->foundation, defaultCandidate
			);
548 549
		}
		if (!iceRestarted) {
550
			bool losingPairsAdded = false;
Simon Morlat's avatar
Simon Morlat committed
551
			for (int j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_ICE_REMOTE_CANDIDATES; j++) {
552 553 554 555 556
				const SalIceRemoteCandidate *remoteCandidate = &stream->ice_remote_candidates[j];
				const char *addr = nullptr;
				int port = 0;
				int componentID = j + 1;
				if (remoteCandidate->addr[0] == '\0') break;
Ronan's avatar
Ronan committed
557
				getIceDefaultAddrAndPort(static_cast<uint16_t>(componentID), md, stream, &addr, &port);
558 559 560

				// If we receive a re-invite and we finished ICE processing on our side, use the candidates given by the remote.
				if (j == 0)
561
					ice_check_list_unselect_valid_pairs(cl);
562

563 564 565 566 567 568
				int remoteFamily = AF_INET;
				if (strchr(remoteCandidate->addr, ':'))
					remoteFamily = AF_INET6;
				int family = AF_INET;
				if (strchr(addr, ':'))
					family = AF_INET6;
Ronan's avatar
Ronan committed
569
				ice_add_losing_pair(cl, static_cast<uint16_t>(j + 1), remoteFamily, remoteCandidate->addr, remoteCandidate->port, family, addr, port);
570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601
				losingPairsAdded = true;
			}
			if (losingPairsAdded)
				ice_check_list_check_completed(cl);
		}
	}
}

/** Return values:
 *  1: STUN gathering is started
 *  0: no STUN gathering is started, but it's ok to proceed with ICE anyway (with local candidates only or because STUN gathering was already done before)
 * -1: no gathering started and something went wrong with local candidates. There is no way to start the ICE session.
 */
int IceAgent::gatherIceCandidates () {
	if (!iceSession)
		return -1;
	IceCheckList *audioCl = ice_session_check_list(iceSession, mediaSession.getPrivate()->getStreamIndex(LinphoneStreamTypeAudio));
	IceCheckList *videoCl = ice_session_check_list(iceSession, mediaSession.getPrivate()->getStreamIndex(LinphoneStreamTypeVideo));
	IceCheckList *textCl = ice_session_check_list(iceSession, mediaSession.getPrivate()->getStreamIndex(LinphoneStreamTypeText));
	if (!audioCl && !videoCl && !textCl)
		return -1;

	const struct addrinfo *ai = nullptr;
	LinphoneNatPolicy *natPolicy = mediaSession.getPrivate()->getNatPolicy();
	if (natPolicy && linphone_nat_policy_stun_server_activated(natPolicy)) {
		ai = linphone_nat_policy_get_stun_server_addrinfo(natPolicy);
		if (ai)
			ai = getIcePreferredStunServerAddrinfo(ai);
		else
			lWarning() << "Failed to resolve STUN server for ICE gathering, continuing without STUN";
	} else
		lWarning() << "ICE is used without STUN server";
602
	LinphoneCore *core = mediaSession.getCore()->getCCore();
603 604 605
	ice_session_enable_forced_relay(iceSession, core->forced_ice_relay);
	ice_session_enable_short_turn_refresh(iceSession, core->short_turn_refresh);

606
	// Gather local host candidates.
607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623
	char localAddr[64];
	if (mediaSession.getPrivate()->getAf() == AF_INET6) {
		if (linphone_core_get_local_ip_for(AF_INET6, nullptr, localAddr) < 0) {
			lError() << "Fail to get local IPv6";
		} else
			addLocalIceCandidates(AF_INET6, localAddr, audioCl, videoCl, textCl);
	}
	if (linphone_core_get_local_ip_for(AF_INET, nullptr, localAddr) < 0) {
		if (mediaSession.getPrivate()->getAf() != AF_INET6) {
			lError() << "Fail to get local IPv4";
			return -1;
		}
	} else
		addLocalIceCandidates(AF_INET, localAddr, audioCl, videoCl, textCl);
	if (ai && natPolicy && linphone_nat_policy_stun_server_activated(natPolicy)) {
		string server = linphone_nat_policy_get_stun_server(natPolicy);
		lInfo() << "ICE: gathering candidates from [" << server << "] using " << (linphone_nat_policy_turn_enabled(natPolicy) ? "TURN" : "STUN");
624
		// Gather local srflx candidates.
625
		ice_session_enable_turn(iceSession, linphone_nat_policy_turn_enabled(natPolicy));
626
		ice_session_set_stun_auth_requested_cb(iceSession, MediaSessionPrivate::stunAuthRequestedCb, mediaSession.getPrivate());
627 628 629 630 631 632 633 634 635 636
		return ice_session_gather_candidates(iceSession, ai->ai_addr, (socklen_t)ai->ai_addrlen) ? 1 : 0;
	} else {
		lInfo() << "ICE: bypass candidates gathering";
		ice_session_compute_candidates_foundations(iceSession);
		ice_session_eliminate_redundant_candidates(iceSession);
		ice_session_choose_default_candidates(iceSession);
	}
	return 0;
}

637 638 639 640 641 642 643
void IceAgent::getIceDefaultAddrAndPort (
	uint16_t componentID,
	const SalMediaDescription *md,
	const SalStreamDescription *stream,
	const char **addr,
	int *port
) {
644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659
	if (componentID == 1) {
		*addr = stream->rtp_addr;
		*port = stream->rtp_port;
	} else if (componentID == 2) {
		*addr = stream->rtcp_addr;
		*port = stream->rtcp_port;
	} else
		return;
	if ((*addr)[0] == '\0') *addr = md->addr;
}

/**
 * Choose the preferred IP address to use to contact the STUN server from the list of IP addresses
 * the DNS resolution returned. If a NAT64 address is present, use it, otherwise if an IPv4 address
 * is present, use it, otherwise use an IPv6 address if it is present.
 */
660 661
const struct addrinfo *IceAgent::getIcePreferredStunServerAddrinfo (const struct addrinfo *ai) {
	// Search for NAT64 addrinfo.
662 663 664 665 666
	const struct addrinfo *it = ai;
	while (it) {
		if (it->ai_family == AF_INET6) {
			struct sockaddr_storage ss;
			socklen_t sslen = sizeof(ss);
667
			memset(&ss, 0, sizeof(ss));
668 669 670 671 672 673 674
			bctbx_sockaddr_remove_nat64_mapping(it->ai_addr, (struct sockaddr *)&ss, &sslen);
			if (ss.ss_family == AF_INET) break;
		}
		it = it->ai_next;
	}
	const struct addrinfo *preferredAi = it;
	if (!preferredAi) {
675
		// Search for IPv4 addrinfo.
676 677 678 679 680 681 682 683 684 685 686
		it = ai;
		while (it) {
			if (it->ai_family == AF_INET)
				break;
			if ((it->ai_family == AF_INET6) && (it->ai_flags & AI_V4MAPPED))
				break;
			it = it->ai_next;
		}
		preferredAi = it;
	}
	if (!preferredAi) {
687
		// Search for IPv6 addrinfo.
688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718
		it = ai;
		while (it) {
			if (it->ai_family == AF_INET6)
				break;
			it = it->ai_next;
		}
		preferredAi = it;
	}
	return preferredAi;
}

bool IceAgent::iceParamsFoundInRemoteMediaDescription (const SalMediaDescription *md) {
	if ((md->ice_pwd[0] != '\0') && (md->ice_ufrag[0] != '\0'))
		return true;
	bool found = false;
	for (int i = 0; i < md->nb_streams; i++) {
		const SalStreamDescription *stream = &md->streams[i];
		IceCheckList *cl = ice_session_check_list(iceSession, i);
		if (cl) {
			if ((stream->ice_pwd[0] != '\0') && (stream->ice_ufrag[0] != '\0'))
				found = true;
			else {
				found = false;
				break;
			}
		}
	}
	return found;
}

void IceAgent::updateIceStateInCallStatsForStream (LinphoneCallStats *stats, IceCheckList *cl) {
719
	if (ice_check_list_state(cl) != ICL_Completed) {
720
		_linphone_call_stats_set_ice_state(stats, LinphoneIceStateFailed);
721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740
		return;
	}

	switch (ice_check_list_selected_valid_candidate_type(cl)) {
		case ICT_HostCandidate:
			_linphone_call_stats_set_ice_state(stats, LinphoneIceStateHostConnection);
			break;
		case ICT_ServerReflexiveCandidate:
		case ICT_PeerReflexiveCandidate:
			_linphone_call_stats_set_ice_state(stats, LinphoneIceStateReflexiveConnection);
			break;
		case ICT_RelayedCandidate:
			_linphone_call_stats_set_ice_state(stats, LinphoneIceStateRelayConnection);
			break;
		case ICT_CandidateInvalid:
		case ICT_CandidateTypeMax:
			// Shall not happen.
			L_ASSERT(false);
			break;
	}
741 742
}

Ghislain MARY's avatar
Ghislain MARY committed
743 744 745
bool IceAgent::checkIceReinviteNeedsDeferedResponse(SalMediaDescription *md) {
	if (!iceSession || (ice_session_state(iceSession) != IS_Running))
		return false;
746

Ghislain MARY's avatar
Ghislain MARY committed
747
	for (int i = 0; i < md->nb_streams; i++) {
748
		SalStreamDescription *stream = &md->streams[i];
Ghislain MARY's avatar
Ghislain MARY committed
749 750 751
		IceCheckList *cl = ice_session_check_list(iceSession, i);
		if (!cl)
			continue;
752

Ghislain MARY's avatar
Ghislain MARY committed
753
		if (stream->ice_mismatch)
754
			return false;
Ghislain MARY's avatar
Ghislain MARY committed
755
		if ((stream->rtp_port == 0) || (ice_check_list_state(cl) != ICL_Running))
756 757
			continue;

Ghislain MARY's avatar
Ghislain MARY committed
758
		for (int j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_ICE_REMOTE_CANDIDATES; j++) {
759
			const SalIceRemoteCandidate *remote_candidate = &stream->ice_remote_candidates[j];
Ghislain MARY's avatar
Ghislain MARY committed
760 761
			if (remote_candidate->addr[0] != '\0')
				return true;
762 763 764 765 766
		}
	}
	return false;
}

767
LINPHONE_END_NAMESPACE