misc.c 40.1 KB
Newer Older
aymeric's avatar
aymeric committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

/*
linphone
Copyright (C) 2000  Simon MORLAT (simon.morlat@linphone.org)

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.

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
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#include "private.h"
22
#include "lpconfig.h"
aymeric's avatar
aymeric committed
23 24 25
#include "mediastreamer2/mediastream.h"
#include <stdlib.h>
#include <stdio.h>
26
#ifdef HAVE_SIGHANDLER_T
aymeric's avatar
aymeric committed
27
#include <signal.h>
28
#endif /*HAVE_SIGHANDLER_T*/
Simon Morlat's avatar
Simon Morlat committed
29 30 31 32

#include <string.h>
#if !defined(_WIN32_WCE)
#include <errno.h>
aymeric's avatar
aymeric committed
33 34
#include <sys/types.h>
#include <sys/stat.h>
Sylvain Berfini's avatar
Sylvain Berfini committed
35 36 37
#if _MSC_VER
#include <io.h>
#else
aymeric's avatar
aymeric committed
38
#include <unistd.h>
Sylvain Berfini's avatar
Sylvain Berfini committed
39
#endif
aymeric's avatar
aymeric committed
40
#include <fcntl.h>
Jehan Monnier's avatar
Jehan Monnier committed
41
#endif /*_WIN32_WCE*/
aymeric's avatar
aymeric committed
42

43
#undef snprintf
smorlat's avatar
smorlat committed
44
#include <ortp/stun.h>
aymeric's avatar
aymeric committed
45

46 47 48 49
#ifdef HAVE_GETIFADDRS
#include <net/if.h>
#include <ifaddrs.h>
#endif
50
#include <math.h>
51 52 53 54
#if _MSC_VER
#define snprintf _snprintf
#define popen _popen
#define pclose _pclose
Sylvain Berfini's avatar
Sylvain Berfini committed
55
#endif
56 57

#if !defined(WIN32)
aymeric's avatar
aymeric committed
58 59 60 61 62 63 64

static char lock_name[80];
static char lock_set=0;
/* put a lock file in /tmp. this is called when linphone runs as a daemon*/
int set_lock_file()
{
	FILE *lockfile;
65

aymeric's avatar
aymeric committed
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
	snprintf(lock_name,80,"/tmp/linphone.%i",getuid());
	lockfile=fopen(lock_name,"w");
	if (lockfile==NULL)
	{
		printf("Failed to create lock file.\n");
		return(-1);
	}
	fprintf(lockfile,"%i",getpid());
	fclose(lockfile);
	lock_set=1;
	return(0);
}

/* looks if there is a lock file. If presents return its content (the pid of the already running linphone), if not found, returns -1*/
int get_lock_file()
{
	int pid;
	FILE *lockfile;
84

aymeric's avatar
aymeric committed
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
	snprintf(lock_name,80,"/tmp/linphone.%i",getuid());
	lockfile=fopen(lock_name,"r");
	if (lockfile==NULL)
		return(-1);
	if (fscanf(lockfile,"%i",&pid)!=1){
		ms_warning("Could not read pid in lock file.");
		fclose(lockfile);
		return -1;
	}
	fclose(lockfile);
	return pid;
}

/* remove the lock file if it was set*/
int remove_lock_file()
{
	int err=0;
	if (lock_set)
	{
		err=unlink(lock_name);
		lock_set=0;
	}
	return(err);
}

#endif

char *int2str(int number)
{
	char *numstr=ms_malloc(10);
jehan's avatar
jehan committed
115
	snprintf(numstr,10,"%i",number);
aymeric's avatar
aymeric committed
116 117 118 119 120
	return numstr;
}

void check_sound_device(LinphoneCore *lc)
{
Simon Morlat's avatar
Simon Morlat committed
121
#ifdef _linux
Jehan Monnier's avatar
Jehan Monnier committed
122 123
	int fd=0;
	int len;
aymeric's avatar
aymeric committed
124 125 126 127 128 129 130
	int a;
	char *file=NULL;
	char *i810_audio=NULL;
	char *snd_pcm_oss=NULL;
	char *snd_mixer_oss=NULL;
	char *snd_pcm=NULL;
	fd=open("/proc/modules",O_RDONLY);
Simon Morlat's avatar
Simon Morlat committed
131

aymeric's avatar
aymeric committed
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
	if (fd>0){
		/* read the entire /proc/modules file and check if sound conf seems correct */
		/*a=fstat(fd,&statbuf);
		if (a<0) ms_warning("Can't stat /proc/modules:%s.",strerror(errno));
		len=statbuf.st_size;
		if (len==0) ms_warning("/proc/modules has zero size!");
		*/
		/***** fstat does not work on /proc/modules for unknown reason *****/
		len=6000;
		file=ms_malloc(len+1);
		a=read(fd,file,len);
		if (a<len) file=ms_realloc(file,a+1);
		file[a]='\0';
		i810_audio=strstr(file,"i810_audio");
		if (i810_audio!=NULL){
			/* I'm sorry i put this warning in comments because
			 * i don't use yet the right driver !! */
/*			lc->vtable.display_warning(lc,_("You are currently using the i810_audio driver.\nThis driver is buggy and so does not work with Linphone.\nWe suggest that you replace it by its equivalent ALSA driver,\neither with packages from your distribution, or by downloading\nALSA drivers at http://www.alsa-project.org."));*/
			goto end;
		}
		snd_pcm=strstr(file,"snd-pcm");
		if (snd_pcm!=NULL){
			snd_pcm_oss=strstr(file,"snd-pcm-oss");
			snd_mixer_oss=strstr(file,"snd-mixer-oss");
			if (snd_pcm_oss==NULL){
				lc->vtable.display_warning(lc,_("Your computer appears to be using ALSA sound drivers.\nThis is the best choice. However the pcm oss emulation module\nis missing and linphone needs it. Please execute\n'modprobe snd-pcm-oss' as root to load it."));
			}
			if (snd_mixer_oss==NULL){
				lc->vtable.display_warning(lc,_("Your computer appears to be using ALSA sound drivers.\nThis is the best choice. However the mixer oss emulation module\nis missing and linphone needs it. Please execute\n 'modprobe snd-mixer-oss' as root to load it."));
			}
		}
	}else {
Simon Morlat's avatar
Simon Morlat committed
164

aymeric's avatar
aymeric committed
165 166 167 168
		ms_warning("Could not open /proc/modules.");
	}
	/* now check general volume. Some user forget to rise it and then complain that linphone is
	not working */
169
	/* but some other users complain that linphone should not change levels...
aymeric's avatar
aymeric committed
170 171 172 173 174 175 176 177 178 179 180
	if (lc->sound_conf.sndcard!=NULL){
		a=snd_card_get_level(lc->sound_conf.sndcard,SND_CARD_LEVEL_GENERAL);
		if (a<50){
			ms_warning("General level is quite low (%i). Linphone rises it up for you.",a);
			snd_card_set_level(lc->sound_conf.sndcard,SND_CARD_LEVEL_GENERAL,80);
		}
	}
	*/
	end:
	if (file!=NULL) ms_free(file);
	if (fd>0) close(fd);
Simon Morlat's avatar
Simon Morlat committed
181
#endif
aymeric's avatar
aymeric committed
182 183 184 185 186 187
}

#define UDP_HDR_SZ 8
#define RTP_HDR_SZ 12
#define IP4_HDR_SZ 20   /*20 is the minimum, but there may be some options*/

188
static void payload_type_set_enable(PayloadType *pt,int value)
aymeric's avatar
aymeric committed
189 190
{
	if ((value)!=0) payload_type_set_flag(pt,PAYLOAD_TYPE_ENABLED); \
191
	else payload_type_unset_flag(pt,PAYLOAD_TYPE_ENABLED);
aymeric's avatar
aymeric committed
192 193
}

194
static bool_t payload_type_enabled(const PayloadType *pt) {
aymeric's avatar
aymeric committed
195 196 197
	return (((pt)->flags & PAYLOAD_TYPE_ENABLED)!=0);
}

198 199
bool_t linphone_core_payload_type_enabled(LinphoneCore *lc, const PayloadType *pt){
	if (ms_list_find(lc->codecs_conf.audio_codecs, (PayloadType*) pt) || ms_list_find(lc->codecs_conf.video_codecs, (PayloadType*)pt)){
200 201 202 203
		return payload_type_enabled(pt);
	}
	ms_error("Getting enablement status of codec not in audio or video list of PayloadType !");
	return FALSE;
aymeric's avatar
aymeric committed
204
}
205 206 207 208

int linphone_core_enable_payload_type(LinphoneCore *lc, PayloadType *pt, bool_t enabled){
	if (ms_list_find(lc->codecs_conf.audio_codecs,pt) || ms_list_find(lc->codecs_conf.video_codecs,pt)){
		payload_type_set_enable(pt,enabled);
209
		_linphone_core_codec_config_write(lc);
210 211 212 213
		return 0;
	}
	ms_error("Enabling codec not in audio or video list of PayloadType !");
	return -1;
aymeric's avatar
aymeric committed
214 215
}

216
int linphone_core_get_payload_type_number(LinphoneCore *lc, const PayloadType *pt){
217 218 219
       return payload_type_get_number(pt);
}

220 221 222
const char *linphone_core_get_payload_type_description(LinphoneCore *lc, PayloadType *pt){
	if (ms_filter_codec_supported(pt->mime_type)){
		MSFilterDesc *desc=ms_filter_get_encoder(pt->mime_type);
223 224 225 226 227
#ifdef ENABLE_NLS
		return dgettext("mediastreamer",desc->text);
#else
		return desc->text;
#endif
228 229
	}
	return NULL;
aymeric's avatar
aymeric committed
230 231
}

232

233 234 235 236 237
/*this function makes a special case for speex/8000.
This codec is variable bitrate. The 8kbit/s mode is interesting when having a low upload bandwidth, but its quality
is not very good. We 'd better use its 15kbt/s mode when we have enough bandwidth*/
static int get_codec_bitrate(LinphoneCore *lc, const PayloadType *pt){
	int upload_bw=linphone_core_get_upload_bandwidth(lc);
238
	if (bandwidth_is_greater(upload_bw,129) || (bandwidth_is_greater(upload_bw,33) && !linphone_core_video_enabled(lc)) ) {
239 240 241 242 243 244 245
		if (strcmp(pt->mime_type,"speex")==0 && pt->clock_rate==8000){
			return 15000;
		}
	}
	return pt->normal_bitrate;
}

246 247 248 249
/*
 *((codec-birate*ptime/8) + RTP header + UDP header + IP header)*8/ptime;
 *ptime=1/npacket
 */
250
static double get_audio_payload_bandwidth(LinphoneCore *lc, const PayloadType *pt){
aymeric's avatar
aymeric committed
251 252 253
	double npacket=50;
	double packet_size;
	int bitrate;
254 255 256 257 258
	if (strcmp(payload_type_get_mime(&payload_type_aaceld_44k), payload_type_get_mime(pt))==0) {
		/*special case of aac 44K because ptime= 10ms*/
		npacket=100;
	}
		
259
	bitrate=get_codec_bitrate(lc,pt);
260
	packet_size= (((double)bitrate)/(npacket*8))+UDP_HDR_SZ+RTP_HDR_SZ+IP4_HDR_SZ;
aymeric's avatar
aymeric committed
261 262 263
	return packet_size*8.0*npacket;
}

Simon Morlat's avatar
Simon Morlat committed
264
void linphone_core_update_allocated_audio_bandwidth_in_call(LinphoneCall *call, const PayloadType *pt){
265
	call->audio_bw=(int)(ceil(get_audio_payload_bandwidth(call->core,pt)/1000.0)); /*rounding codec bandwidth should be avoid, specially for AMR*/
Simon Morlat's avatar
Simon Morlat committed
266
	ms_message("Audio bandwidth for this call is %i",call->audio_bw);
aymeric's avatar
aymeric committed
267 268
}

269
void linphone_core_update_allocated_audio_bandwidth(LinphoneCore *lc){
aymeric's avatar
aymeric committed
270 271 272 273 274
	const MSList *elem;
	PayloadType *max=NULL;
	for(elem=linphone_core_get_audio_codecs(lc);elem!=NULL;elem=elem->next){
		PayloadType *pt=(PayloadType*)elem->data;
		if (payload_type_enabled(pt)){
275
			int pt_bitrate=get_codec_bitrate(lc,pt);
aymeric's avatar
aymeric committed
276
			if (max==NULL) max=pt;
277
			else if (max->normal_bitrate<pt_bitrate){
aymeric's avatar
aymeric committed
278 279 280 281 282
				max=pt;
			}
		}
	}
	if (max) {
Simon Morlat's avatar
Simon Morlat committed
283
		lc->audio_bw=(int)(get_audio_payload_bandwidth(lc,max)/1000.0);
aymeric's avatar
aymeric committed
284 285 286
	}
}

287
bool_t linphone_core_is_payload_type_usable_for_bandwidth(LinphoneCore *lc, PayloadType *pt,  int bandwidth_limit)
Simon Morlat's avatar
Simon Morlat committed
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
{
	double codec_band;
	bool_t ret=FALSE;
	
	switch (pt->type){
		case PAYLOAD_AUDIO_CONTINUOUS:
		case PAYLOAD_AUDIO_PACKETIZED:
			codec_band=get_audio_payload_bandwidth(lc,pt);
			ret=bandwidth_is_greater(bandwidth_limit*1000,codec_band);
			/*hack to avoid using uwb codecs when having low bitrate and video*/
			if (bandwidth_is_greater(199,bandwidth_limit)){
				if (linphone_core_video_enabled(lc) && pt->clock_rate>16000){
					ret=FALSE;
				}
			}
			//ms_message("Payload %s: %g",pt->mime_type,codec_band);
			break;
		case PAYLOAD_VIDEO:
			if (bandwidth_limit!=0) {/* infinite (-1) or strictly positive*/
				ret=TRUE;
			}
			else ret=FALSE;
			break;
	}
	return ret;
}

aymeric's avatar
aymeric committed
315 316 317 318
/* return TRUE if codec can be used with bandwidth, FALSE else*/
bool_t linphone_core_check_payload_type_usability(LinphoneCore *lc, PayloadType *pt)
{
	double codec_band;
Simon Morlat's avatar
Simon Morlat committed
319
	int allowed_bw,video_bw;
aymeric's avatar
aymeric committed
320
	bool_t ret=FALSE;
321 322

	linphone_core_update_allocated_audio_bandwidth(lc);
Simon Morlat's avatar
Simon Morlat committed
323
	allowed_bw=get_min_bandwidth(linphone_core_get_download_bandwidth(lc),
aymeric's avatar
aymeric committed
324
					linphone_core_get_upload_bandwidth(lc));
Simon Morlat's avatar
Simon Morlat committed
325 326 327 328
	if (allowed_bw==0) {
		allowed_bw=-1;
		video_bw=1500; /*around 1.5 Mbit/s*/
	}else
329
		video_bw=get_video_bandwidth(allowed_bw,lc->audio_bw);
aymeric's avatar
aymeric committed
330 331 332 333

	switch (pt->type){
		case PAYLOAD_AUDIO_CONTINUOUS:
		case PAYLOAD_AUDIO_PACKETIZED:
334
			codec_band=get_audio_payload_bandwidth(lc,pt);
Simon Morlat's avatar
Simon Morlat committed
335
			ret=bandwidth_is_greater(allowed_bw*1000,codec_band);
336
			/*hack to avoid using uwb codecs when having low bitrate and video*/
Simon Morlat's avatar
Simon Morlat committed
337
			if (bandwidth_is_greater(199,allowed_bw)){
338 339 340 341
				if (linphone_core_video_enabled(lc) && pt->clock_rate>16000){
					ret=FALSE;
				}
			}
aymeric's avatar
aymeric committed
342 343 344
			//ms_message("Payload %s: %g",pt->mime_type,codec_band);
			break;
		case PAYLOAD_VIDEO:
345
			if (video_bw>0){
Simon Morlat's avatar
Simon Morlat committed
346
				pt->normal_bitrate=video_bw*1000;
347 348
				ret=TRUE;
			}
aymeric's avatar
aymeric committed
349 350 351 352 353 354 355
			else ret=FALSE;
			break;
	}
	return ret;
}

bool_t lp_spawn_command_line_sync(const char *command, char **result,int *command_ret){
356
#if !defined(_WIN32_WCE)
aymeric's avatar
aymeric committed
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
	FILE *f=popen(command,"r");
	if (f!=NULL){
		int err;
		*result=ms_malloc(4096);
		err=fread(*result,1,4096-1,f);
		if (err<0){
			ms_warning("Error reading command output:%s",strerror(errno));
			ms_free(result);
			return FALSE;
		}
		(*result)[err]=0;
		err=pclose(f);
		if (command_ret!=NULL) *command_ret=err;
		return TRUE;
	}
372
#endif /*_WIN32_WCE*/
aymeric's avatar
aymeric committed
373 374 375
	return FALSE;
}

smorlat's avatar
smorlat committed
376 377 378 379 380 381 382 383 384 385 386 387 388 389
static ortp_socket_t create_socket(int local_port){
	struct sockaddr_in laddr;
	ortp_socket_t sock;
	int optval;
	sock=socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP);
	if (sock<0) {
		ms_error("Fail to create socket");
		return -1;
	}
	memset (&laddr,0,sizeof(laddr));
	laddr.sin_family=AF_INET;
	laddr.sin_addr.s_addr=INADDR_ANY;
	laddr.sin_port=htons(local_port);
	if (bind(sock,(struct sockaddr*)&laddr,sizeof(laddr))<0){
smorlat's avatar
smorlat committed
390
		ms_error("Bind socket to 0.0.0.0:%i failed: %s",local_port,getSocketError());
smorlat's avatar
smorlat committed
391 392 393 394 395 396 397 398 399 400 401 402
		close_socket(sock);
		return -1;
	}
	optval=1;
	if (setsockopt (sock, SOL_SOCKET, SO_REUSEADDR,
				(SOCKET_OPTION_VALUE)&optval, sizeof (optval))<0){
		ms_warning("Fail to set SO_REUSEADDR");
	}
	set_non_blocking_socket(sock);
	return sock;
}

smorlat's avatar
smorlat committed
403
static int sendStunRequest(int sock, const struct sockaddr *server, socklen_t addrlen, int id, bool_t changeAddr){
smorlat's avatar
smorlat committed
404 405 406 407 408 409 410 411 412
	char buf[STUN_MAX_MESSAGE_SIZE];
	int len = STUN_MAX_MESSAGE_SIZE;
	StunAtrString username;
   	StunAtrString password;
	StunMessage req;
	int err;
	memset(&req, 0, sizeof(StunMessage));
	memset(&username,0,sizeof(username));
	memset(&password,0,sizeof(password));
smorlat's avatar
smorlat committed
413
	stunBuildReqSimple( &req, &username, changeAddr , changeAddr , id);
aymeric's avatar
aymeric committed
414
	len = stunEncodeMessage( &req, buf, len, &password);
smorlat's avatar
smorlat committed
415 416 417 418 419 420 421 422 423 424 425 426
	if (len<=0){
		ms_error("Fail to encode stun message.");
		return -1;
	}
	err=sendto(sock,buf,len,0,server,addrlen);
	if (err<0){
		ms_error("sendto failed: %s",strerror(errno));
		return -1;
	}
	return 0;
}

427
int parse_hostname_to_addr(const char *server, struct sockaddr_storage *ss, socklen_t *socklen){
smorlat's avatar
smorlat committed
428
	struct addrinfo hints,*res=NULL;
429 430
	int family = PF_INET;
	int port_int = 3478;
smorlat's avatar
smorlat committed
431
	int ret;
432
	char port[6];
smorlat's avatar
smorlat committed
433
	char host[NI_MAXHOST];
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449
	char *p1, *p2;
	if ((sscanf(server, "[%64[^]]]:%d", host, &port_int) == 2) || (sscanf(server, "[%64[^]]]", host) == 1)) {
		family = PF_INET6;
	} else {
		p1 = strchr(server, ':');
		p2 = strrchr(server, ':');
		if (p1 && p2 && (p1 != p2)) {
			family = PF_INET6;
			host[NI_MAXHOST-1]='\0';
			strncpy(host, server, sizeof(host) - 1);
		} else if (sscanf(server, "%[^:]:%d", host, &port_int) != 2) {
			host[NI_MAXHOST-1]='\0';
			strncpy(host, server, sizeof(host) - 1);
		}
	}
	snprintf(port, sizeof(port), "%d", port_int);
smorlat's avatar
smorlat committed
450
	memset(&hints,0,sizeof(hints));
451
	hints.ai_family=family;
smorlat's avatar
smorlat committed
452 453 454 455 456 457 458 459 460 461 462 463 464 465
	hints.ai_socktype=SOCK_DGRAM;
	hints.ai_protocol=IPPROTO_UDP;
	ret=getaddrinfo(host,port,&hints,&res);
	if (ret!=0){
		ms_error("getaddrinfo() failed for %s:%s : %s",host,port,gai_strerror(ret));
		return -1;
	}
	if (!res) return -1;
	memcpy(ss,res->ai_addr,res->ai_addrlen);
	*socklen=res->ai_addrlen;
	freeaddrinfo(res);
	return 0;
}

smorlat's avatar
smorlat committed
466
static int recvStunResponse(ortp_socket_t sock, char *ipaddr, int *port, int *id){
smorlat's avatar
smorlat committed
467 468 469 470 471 472
	char buf[STUN_MAX_MESSAGE_SIZE];
   	int len = STUN_MAX_MESSAGE_SIZE;
	StunMessage resp;
	len=recv(sock,buf,len,0);
	if (len>0){
		struct in_addr ia;
aymeric's avatar
aymeric committed
473 474
		stunParseMessage(buf,len, &resp );
		*id=resp.msgHdr.tr_id.octet[0];
475 476 477 478 479 480 481
		if (resp.hasXorMappedAddress){
			*port = resp.xorMappedAddress.ipv4.port;
			ia.s_addr=htonl(resp.xorMappedAddress.ipv4.addr);
		}else if (resp.hasMappedAddress){
			*port = resp.mappedAddress.ipv4.port;
			ia.s_addr=htonl(resp.mappedAddress.ipv4.addr);
		}else return -1;
smorlat's avatar
smorlat committed
482 483 484 485 486
		strncpy(ipaddr,inet_ntoa(ia),LINPHONE_IPADDR_SIZE);
	}
	return len;
}

Simon Morlat's avatar
Simon Morlat committed
487
/* this functions runs a simple stun test and return the number of milliseconds to complete the tests, or -1 if the test were failed.*/
488
int linphone_core_run_stun_tests(LinphoneCore *lc, LinphoneCall *call){
smorlat's avatar
smorlat committed
489
	const char *server=linphone_core_get_stun_server(lc);
490 491
	StunCandidate *ac=&call->ac;
	StunCandidate *vc=&call->vc;
Simon Morlat's avatar
Simon Morlat committed
492
	
smorlat's avatar
smorlat committed
493 494
	if (lc->sip_conf.ipv6_enabled){
		ms_warning("stun support is not implemented for ipv6");
Simon Morlat's avatar
Simon Morlat committed
495
		return -1;
smorlat's avatar
smorlat committed
496 497 498 499 500
	}
	if (server!=NULL){
		struct sockaddr_storage ss;
		socklen_t ss_len;
		ortp_socket_t sock1=-1, sock2=-1;
501
		int loops=0;
smorlat's avatar
smorlat committed
502 503
		bool_t video_enabled=linphone_core_video_enabled(lc);
		bool_t got_audio,got_video;
smorlat's avatar
smorlat committed
504
		bool_t cone_audio=FALSE,cone_video=FALSE;
smorlat's avatar
smorlat committed
505
		struct timeval init,cur;
Simon Morlat's avatar
Simon Morlat committed
506 507
		double elapsed;
		int ret=0;
508
		
509
		if (parse_hostname_to_addr(server,&ss,&ss_len)<0){
smorlat's avatar
smorlat committed
510
			ms_error("Fail to parser stun server address: %s",server);
Simon Morlat's avatar
Simon Morlat committed
511
			return -1;
smorlat's avatar
smorlat committed
512 513 514
		}
		if (lc->vtable.display_status!=NULL)
			lc->vtable.display_status(lc,_("Stun lookup in progress..."));
515

smorlat's avatar
smorlat committed
516
		/*create the two audio and video RTP sockets, and send STUN message to our stun server */
517
		sock1=create_socket(call->audio_port);
Simon Morlat's avatar
Simon Morlat committed
518
		if (sock1==-1) return -1;
smorlat's avatar
smorlat committed
519
		if (video_enabled){
520
			sock2=create_socket(call->video_port);
Simon Morlat's avatar
Simon Morlat committed
521
			if (sock2==-1) return -1;
smorlat's avatar
smorlat committed
522
		}
smorlat's avatar
smorlat committed
523 524 525 526
		got_audio=FALSE;
		got_video=FALSE;
		gettimeofday(&init,NULL);
		do{
Simon Morlat's avatar
Simon Morlat committed
527
			
smorlat's avatar
smorlat committed
528
			int id;
529 530 531 532 533 534 535 536 537
			if (loops%20==0){
				ms_message("Sending stun requests...");
				sendStunRequest(sock1,(struct sockaddr*)&ss,ss_len,11,TRUE);
				sendStunRequest(sock1,(struct sockaddr*)&ss,ss_len,1,FALSE);
				if (sock2!=-1){
					sendStunRequest(sock2,(struct sockaddr*)&ss,ss_len,22,TRUE);
					sendStunRequest(sock2,(struct sockaddr*)&ss,ss_len,2,FALSE);
				}
			}
538
			ms_usleep(10000);
smorlat's avatar
smorlat committed
539

540 541
			if (recvStunResponse(sock1,ac->addr,
						&ac->port,&id)>0){
smorlat's avatar
smorlat committed
542
				ms_message("STUN test result: local audio port maps to %s:%i",
543 544
						ac->addr,
						ac->port);
smorlat's avatar
smorlat committed
545 546
				if (id==11)
					cone_audio=TRUE;
smorlat's avatar
smorlat committed
547 548
				got_audio=TRUE;
			}
549 550
			if (recvStunResponse(sock2,vc->addr,
							&vc->port,&id)>0){
smorlat's avatar
smorlat committed
551
				ms_message("STUN test result: local video port maps to %s:%i",
552 553
					vc->addr,
					vc->port);
smorlat's avatar
smorlat committed
554 555
				if (id==22)
					cone_video=TRUE;
smorlat's avatar
smorlat committed
556
				got_video=TRUE;
smorlat's avatar
smorlat committed
557 558 559
			}
			gettimeofday(&cur,NULL);
			elapsed=((cur.tv_sec-init.tv_sec)*1000.0) +  ((cur.tv_usec-init.tv_usec)/1000.0);
560 561
			if (elapsed>2000)  {
				ms_message("Stun responses timeout, going ahead.");
Simon Morlat's avatar
Simon Morlat committed
562
				ret=-1;
563 564 565 566
				break;
			}
			loops++;
		}while(!(got_audio && (got_video||sock2==-1)  ) );
Simon Morlat's avatar
Simon Morlat committed
567
		if (ret==0) ret=(int)elapsed;
smorlat's avatar
smorlat committed
568 569
		if (!got_audio){
			ms_error("No stun server response for audio port.");
smorlat's avatar
smorlat committed
570 571
		}else{
			if (!cone_audio) {
572
				ms_message("NAT is symmetric for audio port");
smorlat's avatar
smorlat committed
573
			}
smorlat's avatar
smorlat committed
574
		}
575
		if (sock2!=-1){
smorlat's avatar
smorlat committed
576 577 578 579
			if (!got_video){
				ms_error("No stun server response for video port.");
			}else{
				if (!cone_video) {
580
					ms_message("NAT is symmetric for video port.");
smorlat's avatar
smorlat committed
581 582
				}
			}
smorlat's avatar
smorlat committed
583
		}
smorlat's avatar
smorlat committed
584
		close_socket(sock1);
585
		if (sock2!=-1) close_socket(sock2);
Simon Morlat's avatar
Simon Morlat committed
586
		return ret;
smorlat's avatar
smorlat committed
587
	}
Simon Morlat's avatar
Simon Morlat committed
588
	return -1;
smorlat's avatar
smorlat committed
589 590
}

591 592 593 594 595 596 597 598 599 600
int linphone_core_get_edge_bw(LinphoneCore *lc){
	int edge_bw=lp_config_get_int(lc->config,"net","edge_bw",20);
	return edge_bw;
}

int linphone_core_get_edge_ptime(LinphoneCore *lc){
	int edge_ptime=lp_config_get_int(lc->config,"net","edge_ptime",100);
	return edge_ptime;
}

Simon Morlat's avatar
Simon Morlat committed
601
void linphone_core_adapt_to_network(LinphoneCore *lc, int ping_time_ms, LinphoneCallParams *params){
Sylvain Berfini's avatar
Sylvain Berfini committed
602
	int threshold;
603
	if (ping_time_ms>0 && lp_config_get_int(lc->config,"net","activate_edge_workarounds",0)==1){
604
		ms_message("Stun server ping time is %i ms",ping_time_ms);
Sylvain Berfini's avatar
Sylvain Berfini committed
605
		threshold=lp_config_get_int(lc->config,"net","edge_ping_time",500);
Simon Morlat's avatar
Simon Morlat committed
606 607
		
		if (ping_time_ms>threshold){
608
			/* we might be in a 2G network*/
jehan's avatar
jehan committed
609
			params->low_bandwidth=TRUE;
Simon Morlat's avatar
Simon Morlat committed
610 611
		}/*else use default settings */
	}
612 613 614 615 616
	if (params->low_bandwidth){
		params->up_bw=params->down_bw=linphone_core_get_edge_bw(lc);
		params->up_ptime=params->down_ptime=linphone_core_get_edge_ptime(lc);
		params->has_video=FALSE;
	}
Simon Morlat's avatar
Simon Morlat committed
617 618 619 620
}



621
int linphone_core_gather_ice_candidates(LinphoneCore *lc, LinphoneCall *call)
622
{
623
	char local_addr[64];
624 625
	struct sockaddr_storage ss;
	socklen_t ss_len;
626 627
	IceCheckList *audio_check_list;
	IceCheckList *video_check_list;
628 629
	const char *server = linphone_core_get_stun_server(lc);

630 631 632
	if ((server == NULL) || (call->ice_session == NULL)) return -1;
	audio_check_list = ice_session_check_list(call->ice_session, 0);
	video_check_list = ice_session_check_list(call->ice_session, 1);
633
	if (audio_check_list == NULL) return -1;
634

635 636
	if (lc->sip_conf.ipv6_enabled){
		ms_warning("stun support is not implemented for ipv6");
637
		return -1;
638 639 640 641
	}

	if (parse_hostname_to_addr(server, &ss, &ss_len) < 0) {
		ms_error("Fail to parser stun server address: %s", server);
642
		return -1;
643 644 645 646
	}
	if (lc->vtable.display_status != NULL)
		lc->vtable.display_status(lc, _("ICE local candidates gathering in progress..."));

647
	/* Gather local host candidates. */
648
	if (linphone_core_get_local_ip_for(AF_INET, server, local_addr) < 0) {
649
		ms_error("Fail to get local ip");
650
		return -1;
651
	}
652 653 654 655 656 657 658
	if ((ice_check_list_state(audio_check_list) != ICL_Completed) && (ice_check_list_candidates_gathered(audio_check_list) == FALSE)) {
		ice_add_local_candidate(audio_check_list, "host", local_addr, call->audio_port, 1, NULL);
		ice_add_local_candidate(audio_check_list, "host", local_addr, call->audio_port + 1, 2, NULL);
		call->stats[LINPHONE_CALL_STATS_AUDIO].ice_state = LinphoneIceStateInProgress;
	}
	if (call->params.has_video && (video_check_list != NULL)
		&& (ice_check_list_state(video_check_list) != ICL_Completed) && (ice_check_list_candidates_gathered(video_check_list) == FALSE)) {
659 660
		ice_add_local_candidate(video_check_list, "host", local_addr, call->video_port, 1, NULL);
		ice_add_local_candidate(video_check_list, "host", local_addr, call->video_port + 1, 2, NULL);
661
		call->stats[LINPHONE_CALL_STATS_VIDEO].ice_state = LinphoneIceStateInProgress;
662
	}
663

jehan's avatar
jehan committed
664
	ms_message("ICE: gathering candidate from [%s]",server);
665
	/* Gather local srflx candidates. */
666
	ice_session_gather_candidates(call->ice_session, ss, ss_len);
667
	return 0;
668 669
}

670 671 672 673
void linphone_core_update_ice_state_in_call_stats(LinphoneCall *call)
{
	IceCheckList *audio_check_list;
	IceCheckList *video_check_list;
Ghislain MARY's avatar
Ghislain MARY committed
674
	IceSessionState session_state;
675 676 677 678 679 680

	if (call->ice_session == NULL) return;
	audio_check_list = ice_session_check_list(call->ice_session, 0);
	video_check_list = ice_session_check_list(call->ice_session, 1);
	if (audio_check_list == NULL) return;

Ghislain MARY's avatar
Ghislain MARY committed
681 682
	session_state = ice_session_state(call->ice_session);
	if ((session_state == IS_Completed) || ((session_state == IS_Failed) && (ice_session_has_completed_check_list(call->ice_session) == TRUE))) {
683 684
		if (ice_check_list_state(audio_check_list) == ICL_Completed) {
			switch (ice_check_list_selected_valid_candidate_type(audio_check_list)) {
Ghislain MARY's avatar
Ghislain MARY committed
685
				case ICT_HostCandidate:
686
					call->stats[LINPHONE_CALL_STATS_AUDIO].ice_state = LinphoneIceStateHostConnection;
Ghislain MARY's avatar
Ghislain MARY committed
687 688 689
					break;
				case ICT_ServerReflexiveCandidate:
				case ICT_PeerReflexiveCandidate:
690
					call->stats[LINPHONE_CALL_STATS_AUDIO].ice_state = LinphoneIceStateReflexiveConnection;
Ghislain MARY's avatar
Ghislain MARY committed
691 692
					break;
				case ICT_RelayedCandidate:
693
					call->stats[LINPHONE_CALL_STATS_AUDIO].ice_state = LinphoneIceStateRelayConnection;
Ghislain MARY's avatar
Ghislain MARY committed
694 695
					break;
			}
696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715
		} else {
			call->stats[LINPHONE_CALL_STATS_AUDIO].ice_state = LinphoneIceStateFailed;
		}
		if (call->params.has_video && (video_check_list != NULL)) {
			if (ice_check_list_state(video_check_list) == ICL_Completed) {
				switch (ice_check_list_selected_valid_candidate_type(video_check_list)) {
					case ICT_HostCandidate:
						call->stats[LINPHONE_CALL_STATS_VIDEO].ice_state = LinphoneIceStateHostConnection;
						break;
					case ICT_ServerReflexiveCandidate:
					case ICT_PeerReflexiveCandidate:
						call->stats[LINPHONE_CALL_STATS_VIDEO].ice_state = LinphoneIceStateReflexiveConnection;
						break;
					case ICT_RelayedCandidate:
						call->stats[LINPHONE_CALL_STATS_VIDEO].ice_state = LinphoneIceStateRelayConnection;
						break;
				}
			} else {
				call->stats[LINPHONE_CALL_STATS_VIDEO].ice_state = LinphoneIceStateFailed;
			}
Ghislain MARY's avatar
Ghislain MARY committed
716
		}
717 718 719 720 721
	} else if (session_state == IS_Running) {
		call->stats[LINPHONE_CALL_STATS_AUDIO].ice_state = LinphoneIceStateInProgress;
		if (call->params.has_video && (video_check_list != NULL)) {
			call->stats[LINPHONE_CALL_STATS_VIDEO].ice_state = LinphoneIceStateInProgress;
		}
Ghislain MARY's avatar
Ghislain MARY committed
722 723 724 725 726
	} else {
		call->stats[LINPHONE_CALL_STATS_AUDIO].ice_state = LinphoneIceStateFailed;
		if (call->params.has_video && (video_check_list != NULL)) {
			call->stats[LINPHONE_CALL_STATS_VIDEO].ice_state = LinphoneIceStateFailed;
		}
727 728 729
	}
}

730 731
void linphone_core_update_local_media_description_from_ice(SalMediaDescription *desc, IceSession *session)
{
732
	const char *rtp_addr, *rtcp_addr;
733
	IceSessionState session_state = ice_session_state(session);
734
	int nb_candidates;
735
	int i, j;
736
	bool_t result;
737

738 739
	if (session_state == IS_Completed) {
		desc->ice_completed = TRUE;
Ghislain MARY's avatar
Ghislain MARY committed
740 741 742 743 744 745
		result = ice_check_list_selected_valid_local_candidate(ice_session_check_list(session, 0), &rtp_addr, NULL, NULL, NULL);
		if (result == TRUE) {
			strncpy(desc->addr, rtp_addr, sizeof(desc->addr));
		} else {
			ms_warning("If ICE has completed successfully, rtp_addr should be set!");
		}
746 747 748 749
	}
	else {
		desc->ice_completed = FALSE;
	}
750 751
	strncpy(desc->ice_pwd, ice_session_local_pwd(session), sizeof(desc->ice_pwd));
	strncpy(desc->ice_ufrag, ice_session_local_ufrag(session), sizeof(desc->ice_ufrag));
752
	for (i = 0; i < desc->n_active_streams; i++) {
753 754
		SalStreamDescription *stream = &desc->streams[i];
		IceCheckList *cl = ice_session_check_list(session, i);
755
		nb_candidates = 0;
756
		if (cl == NULL) continue;
757
		if (ice_check_list_state(cl) == ICL_Completed) {
758
			stream->ice_completed = TRUE;
759 760 761 762 763 764
			result = ice_check_list_selected_valid_local_candidate(ice_session_check_list(session, i), &rtp_addr, &stream->rtp_port, &rtcp_addr, &stream->rtcp_port);
		} else {
			stream->ice_completed = FALSE;
			result = ice_check_list_default_local_candidate(ice_session_check_list(session, i), &rtp_addr, &stream->rtp_port, &rtcp_addr, &stream->rtcp_port);
		}
		if (result == TRUE) {
765 766 767
			strncpy(stream->rtp_addr, rtp_addr, sizeof(stream->rtp_addr));
			strncpy(stream->rtcp_addr, rtcp_addr, sizeof(stream->rtcp_addr));
		} else {
768 769
			memset(stream->rtp_addr, 0, sizeof(stream->rtp_addr));
			memset(stream->rtcp_addr, 0, sizeof(stream->rtcp_addr));
770
		}
771 772 773 774 775 776 777 778
		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));
779
		stream->ice_mismatch = ice_check_list_is_mismatch(cl);
780
		if ((ice_check_list_state(cl) == ICL_Running) || (ice_check_list_state(cl) == ICL_Completed)) {
781
			memset(stream->ice_candidates, 0, sizeof(stream->ice_candidates));
Ghislain MARY's avatar
Ghislain MARY committed
782
			for (j = 0; j < MIN(ms_list_size(cl->local_candidates), SAL_MEDIA_DESCRIPTION_MAX_ICE_CANDIDATES); j++) {
783
				SalIceCandidate *sal_candidate = &stream->ice_candidates[nb_candidates];
784 785 786 787 788 789 790 791 792 793 794 795
				IceCandidate *ice_candidate = ms_list_nth_data(cl->local_candidates, j);
				const char *default_addr = NULL;
				int default_port = 0;
				if (ice_candidate->componentID == 1) {
					default_addr = stream->rtp_addr;
					default_port = stream->rtp_port;
				} else if (ice_candidate->componentID == 2) {
					default_addr = stream->rtcp_addr;
					default_port = stream->rtcp_port;
				} else continue;
				if (default_addr[0] == '\0') default_addr = desc->addr;
				/* 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. */
796
				if ((ice_check_list_state(cl) == ICL_Completed)
797 798 799 800 801 802 803 804 805 806 807 808
					&& !((ice_candidate->taddr.port == default_port) && (strlen(ice_candidate->taddr.ip) == strlen(default_addr)) && (strcmp(ice_candidate->taddr.ip, default_addr) == 0)))
					continue;
				strncpy(sal_candidate->foundation, ice_candidate->foundation, sizeof(sal_candidate->foundation));
				sal_candidate->componentID = ice_candidate->componentID;
				sal_candidate->priority = ice_candidate->priority;
				strncpy(sal_candidate->type, ice_candidate_type(ice_candidate), sizeof(sal_candidate->type));
				strncpy(sal_candidate->addr, ice_candidate->taddr.ip, sizeof(sal_candidate->addr));
				sal_candidate->port = ice_candidate->taddr.port;
				if ((ice_candidate->base != NULL) && (ice_candidate->base != ice_candidate)) {
					strncpy(sal_candidate->raddr, ice_candidate->base->taddr.ip, sizeof(sal_candidate->raddr));
					sal_candidate->rport = ice_candidate->base->taddr.port;
				}
809
				nb_candidates++;
810 811
			}
		}
812
		if ((ice_check_list_state(cl) == ICL_Completed) && (ice_session_role(session) == IR_Controlling)) {
813 814
			int rtp_port, rtcp_port;
			memset(stream->ice_remote_candidates, 0, sizeof(stream->ice_remote_candidates));
Ghislain MARY's avatar
Ghislain MARY committed
815
			if (ice_check_list_selected_valid_remote_candidate(cl, &rtp_addr, &rtp_port, &rtcp_addr, &rtcp_port) == TRUE) {
816 817 818 819 820 821 822
				strncpy(stream->ice_remote_candidates[0].addr, rtp_addr, sizeof(stream->ice_remote_candidates[0].addr));
				stream->ice_remote_candidates[0].port = rtp_port;
				strncpy(stream->ice_remote_candidates[1].addr, rtcp_addr, sizeof(stream->ice_remote_candidates[1].addr));
				stream->ice_remote_candidates[1].port = rtcp_port;
			} else {
				ms_error("ice: Selected valid remote candidates should be present if the check list is in the Completed state");
			}
823 824 825 826 827
		} else {
			for (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;
			}
828 829 830 831
		}
	}
}

832 833 834 835 836 837 838 839 840 841 842 843
static void get_default_addr_and_port(uint16_t componentID, const SalMediaDescription *md, const SalStreamDescription *stream, const char **addr, int *port)
{
	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;
}

844 845
void linphone_core_update_ice_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md)
{
846 847
	bool_t ice_restarted = FALSE;

848 849
	if ((md->ice_pwd[0] != '\0') && (md->ice_ufrag[0] != '\0')) {
		int i, j;
850 851

		/* Check for ICE restart and set remote credentials. */
852 853 854 855
		if ((strcmp(md->addr, "0.0.0.0") == 0) || (strcmp(md->addr, "::0") == 0)) {
			ice_session_restart(call->ice_session);
			ice_restarted = TRUE;
		} else {
856
			for (i = 0; i < md->n_total_streams; i++) {
857 858 859 860 861 862 863 864 865
				const SalStreamDescription *stream = &md->streams[i];
				IceCheckList *cl = ice_session_check_list(call->ice_session, i);
				if (cl && (strcmp(stream->rtp_addr, "0.0.0.0") == 0)) {
					ice_session_restart(call->ice_session);
					ice_restarted = TRUE;
					break;
				}
			}
		}
866 867 868
		if ((ice_session_remote_ufrag(call->ice_session) == NULL) && (ice_session_remote_pwd(call->ice_session) == NULL)) {
			ice_session_set_remote_credentials(call->ice_session, md->ice_ufrag, md->ice_pwd);
		} else if (ice_session_remote_credentials_changed(call->ice_session, md->ice_ufrag, md->ice_pwd)) {
869 870 871 872
			if (ice_restarted == FALSE) {
				ice_session_restart(call->ice_session);
				ice_restarted = TRUE;
			}
873 874
			ice_session_set_remote_credentials(call->ice_session, md->ice_ufrag, md->ice_pwd);
		}
875
		for (i = 0; i < md->n_total_streams; i++) {
876 877 878 879
			const SalStreamDescription *stream = &md->streams[i];
			IceCheckList *cl = ice_session_check_list(call->ice_session, 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)) {
880 881 882 883
					if (ice_restarted == FALSE) {
						ice_session_restart(call->ice_session);
						ice_restarted = TRUE;
					}
884 885 886 887 888 889 890
					ice_session_set_remote_credentials(call->ice_session, md->ice_ufrag, md->ice_pwd);
					break;
				}
			}
		}

		/* Create ICE check lists if needed and parse ICE attributes. */
891
		for (i = 0; i < md->n_total_streams; i++) {
892 893
			const SalStreamDescription *stream = &md->streams[i];
			IceCheckList *cl = ice_session_check_list(call->ice_session, i);
894
			if ((cl == NULL) && (i < md->n_active_streams)) {
895 896 897 898
				cl = ice_check_list_new();
				ice_session_add_check_list(call->ice_session, cl);
				switch (stream->type) {
					case SalAudio:
Ghislain MARY's avatar
Ghislain MARY committed
899
						if (call->audiostream != NULL) call->audiostream->ms.ice_check_list = cl;
900 901
						break;
					case SalVideo:
Ghislain MARY's avatar
Ghislain MARY committed
902
						if (call->videostream != NULL) call->videostream->ms.ice_check_list = cl;
903 904 905 906 907
						break;
					default:
						break;
				}
			}
908
			if (stream->ice_mismatch == TRUE) {
909
				ice_check_list_set_state(cl, ICL_Failed);
910
			} else if (stream->rtp_port == 0) {
Ghislain MARY's avatar
Ghislain MARY committed
911
				ice_session_remove_check_list(call->ice_session, cl);
912 913 914 915 916 917 918 919 920
			} else {
				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 (j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_ICE_CANDIDATES; j++) {
					const SalIceCandidate *candidate = &stream->ice_candidates[j];
					bool_t default_candidate = FALSE;
					const char *addr = NULL;
					int port = 0;
					if (candidate->addr[0] == '\0') break;
921 922
					if ((candidate->componentID == 0) || (candidate->componentID > 2)) continue;
					get_default_addr_and_port(candidate->componentID, md, stream, &addr, &port);
923 924 925 926 927
					if (addr && (candidate->port == port) && (strlen(candidate->addr) == strlen(addr)) && (strcmp(candidate->addr, addr) == 0))
						default_candidate = TRUE;
					ice_add_remote_candidate(cl, candidate->type, candidate->addr, candidate->port, candidate->componentID,
						candidate->priority, candidate->foundation, default_candidate);
				}
928
				if (ice_restarted == FALSE) {
929
					bool_t losing_pairs_added = FALSE;
930 931 932 933 934 935 936
					for (j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_ICE_REMOTE_CANDIDATES; j++) {
						const SalIceRemoteCandidate *candidate = &stream->ice_remote_candidates[j];
						const char *addr = NULL;
						int port = 0;
						int componentID = j + 1;
						if (candidate->addr[0] == '\0') break;
						get_default_addr_and_port(componentID, md, stream, &addr, &port);
937 938 939 940
						if (j == 0) {
							/* If we receive a re-invite and we finished ICE processing on our side, use the candidates given by the remote. */
							ice_check_list_unselect_valid_pairs(cl);
						}
941 942
						ice_add_losing_pair(cl, j + 1, candidate->addr, candidate->port, addr, port);
						losing_pairs_added = TRUE;
943
					}
944
					if (losing_pairs_added == TRUE) ice_check_list_check_completed(cl);
945
				}
946 947
			}
		}
948
		for (i = ice_session_nb_check_lists(call->ice_session); i > md->n_active_streams; i--) {
949 950
			ice_session_remove_check_list(call->ice_session, ice_session_check_list(call->ice_session, i - 1));
		}
951
		ice_session_check_mismatch(call->ice_session);
952 953 954 955
	} else {
		/* Response from remote does not contain mandatory ICE attributes, delete the session. */
		linphone_call_delete_ice_session(call);
		return;
956
	}
957
	if (ice_session_nb_check_lists(call->ice_session) == 0) {
958 959 960 961
		linphone_call_delete_ice_session(call);
	}
}

962 963 964 965
bool_t linphone_core_media_description_contains_video_stream(const SalMediaDescription *md)
{
	int i;

966 967
	for (i = 0; i < md->n_active_streams; i++) {
		if (md->streams[i].type == SalVideo)
968 <