misc.c 40.3 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

aymeric's avatar
aymeric committed
57 58 59 60 61

#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*/

62
static void payload_type_set_enable(PayloadType *pt,int value)
aymeric's avatar
aymeric committed
63 64
{
	if ((value)!=0) payload_type_set_flag(pt,PAYLOAD_TYPE_ENABLED); \
65
	else payload_type_unset_flag(pt,PAYLOAD_TYPE_ENABLED);
aymeric's avatar
aymeric committed
66 67
}

68
static bool_t payload_type_enabled(const PayloadType *pt) {
aymeric's avatar
aymeric committed
69 70 71
	return (((pt)->flags & PAYLOAD_TYPE_ENABLED)!=0);
}

72 73
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)){
74 75 76 77
		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
78
}
79 80 81 82

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);
83
		_linphone_core_codec_config_write(lc);
84 85 86 87
		return 0;
	}
	ms_error("Enabling codec not in audio or video list of PayloadType !");
	return -1;
aymeric's avatar
aymeric committed
88 89
}

90
int linphone_core_get_payload_type_number(LinphoneCore *lc, const PayloadType *pt){
91 92 93
       return payload_type_get_number(pt);
}

94 95 96
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);
97 98 99 100 101
#ifdef ENABLE_NLS
		return dgettext("mediastreamer",desc->text);
#else
		return desc->text;
#endif
102 103
	}
	return NULL;
aymeric's avatar
aymeric committed
104 105
}

106

107 108 109 110 111
/*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);
112
	if (bandwidth_is_greater(upload_bw,129) || (bandwidth_is_greater(upload_bw,33) && !linphone_core_video_enabled(lc)) ) {
113 114 115 116 117 118 119
		if (strcmp(pt->mime_type,"speex")==0 && pt->clock_rate==8000){
			return 15000;
		}
	}
	return pt->normal_bitrate;
}

120 121 122 123
/*
 *((codec-birate*ptime/8) + RTP header + UDP header + IP header)*8/ptime;
 *ptime=1/npacket
 */
124
static double get_audio_payload_bandwidth(LinphoneCore *lc, const PayloadType *pt){
aymeric's avatar
aymeric committed
125 126 127
	double npacket=50;
	double packet_size;
	int bitrate;
128 129 130 131 132
	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;
	}
		
133
	bitrate=get_codec_bitrate(lc,pt);
134
	packet_size= (((double)bitrate)/(npacket*8))+UDP_HDR_SZ+RTP_HDR_SZ+IP4_HDR_SZ;
aymeric's avatar
aymeric committed
135 136 137
	return packet_size*8.0*npacket;
}

Simon Morlat's avatar
Simon Morlat committed
138
void linphone_core_update_allocated_audio_bandwidth_in_call(LinphoneCall *call, const PayloadType *pt){
139
	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
140
	ms_message("Audio bandwidth for this call is %i",call->audio_bw);
aymeric's avatar
aymeric committed
141 142
}

143
void linphone_core_update_allocated_audio_bandwidth(LinphoneCore *lc){
aymeric's avatar
aymeric committed
144 145 146 147 148
	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)){
149
			int pt_bitrate=get_codec_bitrate(lc,pt);
aymeric's avatar
aymeric committed
150
			if (max==NULL) max=pt;
151
			else if (max->normal_bitrate<pt_bitrate){
aymeric's avatar
aymeric committed
152 153 154 155 156
				max=pt;
			}
		}
	}
	if (max) {
Simon Morlat's avatar
Simon Morlat committed
157
		lc->audio_bw=(int)(get_audio_payload_bandwidth(lc,max)/1000.0);
aymeric's avatar
aymeric committed
158 159 160
	}
}

161
bool_t linphone_core_is_payload_type_usable_for_bandwidth(LinphoneCore *lc, PayloadType *pt,  int bandwidth_limit)
Simon Morlat's avatar
Simon Morlat committed
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
{
	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
189 190 191 192
/* 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
193
	int allowed_bw,video_bw;
aymeric's avatar
aymeric committed
194
	bool_t ret=FALSE;
195 196

	linphone_core_update_allocated_audio_bandwidth(lc);
Simon Morlat's avatar
Simon Morlat committed
197
	allowed_bw=get_min_bandwidth(linphone_core_get_download_bandwidth(lc),
aymeric's avatar
aymeric committed
198
					linphone_core_get_upload_bandwidth(lc));
Simon Morlat's avatar
Simon Morlat committed
199 200 201 202
	if (allowed_bw==0) {
		allowed_bw=-1;
		video_bw=1500; /*around 1.5 Mbit/s*/
	}else
203
		video_bw=get_video_bandwidth(allowed_bw,lc->audio_bw);
aymeric's avatar
aymeric committed
204 205 206 207

	switch (pt->type){
		case PAYLOAD_AUDIO_CONTINUOUS:
		case PAYLOAD_AUDIO_PACKETIZED:
208
			codec_band=get_audio_payload_bandwidth(lc,pt);
Simon Morlat's avatar
Simon Morlat committed
209
			ret=bandwidth_is_greater(allowed_bw*1000,codec_band);
210
			/*hack to avoid using uwb codecs when having low bitrate and video*/
Simon Morlat's avatar
Simon Morlat committed
211
			if (bandwidth_is_greater(199,allowed_bw)){
212 213 214 215
				if (linphone_core_video_enabled(lc) && pt->clock_rate>16000){
					ret=FALSE;
				}
			}
aymeric's avatar
aymeric committed
216 217 218
			//ms_message("Payload %s: %g",pt->mime_type,codec_band);
			break;
		case PAYLOAD_VIDEO:
219
			if (video_bw>0){
Simon Morlat's avatar
Simon Morlat committed
220
				pt->normal_bitrate=video_bw*1000;
221 222
				ret=TRUE;
			}
aymeric's avatar
aymeric committed
223 224 225 226 227 228 229
			else ret=FALSE;
			break;
	}
	return ret;
}

bool_t lp_spawn_command_line_sync(const char *command, char **result,int *command_ret){
230
#if !defined(_WIN32_WCE)
aymeric's avatar
aymeric committed
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
	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;
	}
246
#endif /*_WIN32_WCE*/
aymeric's avatar
aymeric committed
247 248 249
	return FALSE;
}

smorlat's avatar
smorlat committed
250 251 252 253 254 255 256 257 258 259 260 261 262 263
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
264
		ms_error("Bind socket to 0.0.0.0:%i failed: %s",local_port,getSocketError());
smorlat's avatar
smorlat committed
265 266 267 268 269 270 271 272 273 274 275 276
		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
277
static int sendStunRequest(int sock, const struct sockaddr *server, socklen_t addrlen, int id, bool_t changeAddr){
smorlat's avatar
smorlat committed
278 279 280 281 282 283 284 285 286
	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
287
	stunBuildReqSimple( &req, &username, changeAddr , changeAddr , id);
aymeric's avatar
aymeric committed
288
	len = stunEncodeMessage( &req, buf, len, &password);
smorlat's avatar
smorlat committed
289 290 291 292 293 294 295 296 297 298 299 300
	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;
}

301 302
int linphone_parse_host_port(const char *input, char *host, size_t hostlen, int *port){
	char tmphost[NI_MAXHOST]={0};
303
	char *p1, *p2;
304 305 306
	
	if ((sscanf(input, "[%64[^]]]:%d", tmphost, port) == 2) || (sscanf(input, "[%64[^]]]", tmphost) == 1)) {
		
307
	} else {
308 309 310 311 312 313 314
		p1 = strchr(input, ':');
		p2 = strrchr(input, ':');
		if (p1 && p2 && (p1 != p2)) {/* an ipv6 address without port*/
			strncpy(tmphost, input, sizeof(tmphost) - 1);
		} else if (sscanf(input, "%[^:]:%d", tmphost, port) != 2) {
			/*no port*/
			strncpy(tmphost, input, sizeof(tmphost) - 1);
315 316
		}
	}
317 318 319 320 321 322 323 324 325 326 327 328 329
	strncpy(host,tmphost,hostlen);
	return 0;
}

int parse_hostname_to_addr(const char *server, struct sockaddr_storage *ss, socklen_t *socklen, int default_port){
	struct addrinfo hints,*res=NULL;
	char port[6];
	char host[NI_MAXHOST];
	int port_int=default_port;
	int ret;
	
	linphone_parse_host_port(server,host,sizeof(host),&port_int);
	
330
	snprintf(port, sizeof(port), "%d", port_int);
smorlat's avatar
smorlat committed
331
	memset(&hints,0,sizeof(hints));
332
	hints.ai_family=AF_UNSPEC;
smorlat's avatar
smorlat committed
333 334 335 336 337 338 339 340 341 342 343 344 345 346
	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
347
static int recvStunResponse(ortp_socket_t sock, char *ipaddr, int *port, int *id){
smorlat's avatar
smorlat committed
348 349 350 351 352 353
	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
354 355
		stunParseMessage(buf,len, &resp );
		*id=resp.msgHdr.tr_id.octet[0];
356 357 358 359 360 361 362
		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
363 364 365 366 367
		strncpy(ipaddr,inet_ntoa(ia),LINPHONE_IPADDR_SIZE);
	}
	return len;
}

Simon Morlat's avatar
Simon Morlat committed
368
/* this functions runs a simple stun test and return the number of milliseconds to complete the tests, or -1 if the test were failed.*/
369
int linphone_core_run_stun_tests(LinphoneCore *lc, LinphoneCall *call){
smorlat's avatar
smorlat committed
370
	const char *server=linphone_core_get_stun_server(lc);
371 372
	StunCandidate *ac=&call->ac;
	StunCandidate *vc=&call->vc;
Simon Morlat's avatar
Simon Morlat committed
373
	
smorlat's avatar
smorlat committed
374 375
	if (lc->sip_conf.ipv6_enabled){
		ms_warning("stun support is not implemented for ipv6");
Simon Morlat's avatar
Simon Morlat committed
376
		return -1;
smorlat's avatar
smorlat committed
377 378
	}
	if (server!=NULL){
379
		const struct addrinfo *ai=linphone_core_get_stun_server_addrinfo(lc);
smorlat's avatar
smorlat committed
380
		ortp_socket_t sock1=-1, sock2=-1;
381
		int loops=0;
smorlat's avatar
smorlat committed
382 383
		bool_t video_enabled=linphone_core_video_enabled(lc);
		bool_t got_audio,got_video;
smorlat's avatar
smorlat committed
384
		bool_t cone_audio=FALSE,cone_video=FALSE;
smorlat's avatar
smorlat committed
385
		struct timeval init,cur;
Simon Morlat's avatar
Simon Morlat committed
386 387
		double elapsed;
		int ret=0;
388
		
389 390
		if (ai==NULL){
			ms_error("Could not obtain stun server addrinfo.");
Simon Morlat's avatar
Simon Morlat committed
391
			return -1;
smorlat's avatar
smorlat committed
392 393 394
		}
		if (lc->vtable.display_status!=NULL)
			lc->vtable.display_status(lc,_("Stun lookup in progress..."));
395

smorlat's avatar
smorlat committed
396
		/*create the two audio and video RTP sockets, and send STUN message to our stun server */
397
		sock1=create_socket(call->audio_port);
Simon Morlat's avatar
Simon Morlat committed
398
		if (sock1==-1) return -1;
smorlat's avatar
smorlat committed
399
		if (video_enabled){
400
			sock2=create_socket(call->video_port);
Simon Morlat's avatar
Simon Morlat committed
401
			if (sock2==-1) return -1;
smorlat's avatar
smorlat committed
402
		}
smorlat's avatar
smorlat committed
403 404
		got_audio=FALSE;
		got_video=FALSE;
405
		ortp_gettimeofday(&init,NULL);
smorlat's avatar
smorlat committed
406
		do{
Simon Morlat's avatar
Simon Morlat committed
407
			
smorlat's avatar
smorlat committed
408
			int id;
409 410
			if (loops%20==0){
				ms_message("Sending stun requests...");
411 412
				sendStunRequest(sock1,ai->ai_addr,ai->ai_addrlen,11,TRUE);
				sendStunRequest(sock1,ai->ai_addr,ai->ai_addrlen,1,FALSE);
413
				if (sock2!=-1){
414 415
					sendStunRequest(sock2,ai->ai_addr,ai->ai_addrlen,22,TRUE);
					sendStunRequest(sock2,ai->ai_addr,ai->ai_addrlen,2,FALSE);
416 417
				}
			}
418
			ms_usleep(10000);
smorlat's avatar
smorlat committed
419

420 421
			if (recvStunResponse(sock1,ac->addr,
						&ac->port,&id)>0){
smorlat's avatar
smorlat committed
422
				ms_message("STUN test result: local audio port maps to %s:%i",
423 424
						ac->addr,
						ac->port);
smorlat's avatar
smorlat committed
425 426
				if (id==11)
					cone_audio=TRUE;
smorlat's avatar
smorlat committed
427 428
				got_audio=TRUE;
			}
429 430
			if (recvStunResponse(sock2,vc->addr,
							&vc->port,&id)>0){
smorlat's avatar
smorlat committed
431
				ms_message("STUN test result: local video port maps to %s:%i",
432 433
					vc->addr,
					vc->port);
smorlat's avatar
smorlat committed
434 435
				if (id==22)
					cone_video=TRUE;
smorlat's avatar
smorlat committed
436
				got_video=TRUE;
smorlat's avatar
smorlat committed
437
			}
438
			ortp_gettimeofday(&cur,NULL);
smorlat's avatar
smorlat committed
439
			elapsed=((cur.tv_sec-init.tv_sec)*1000.0) +  ((cur.tv_usec-init.tv_usec)/1000.0);
440 441
			if (elapsed>2000)  {
				ms_message("Stun responses timeout, going ahead.");
Simon Morlat's avatar
Simon Morlat committed
442
				ret=-1;
443 444 445 446
				break;
			}
			loops++;
		}while(!(got_audio && (got_video||sock2==-1)  ) );
Simon Morlat's avatar
Simon Morlat committed
447
		if (ret==0) ret=(int)elapsed;
smorlat's avatar
smorlat committed
448 449
		if (!got_audio){
			ms_error("No stun server response for audio port.");
smorlat's avatar
smorlat committed
450 451
		}else{
			if (!cone_audio) {
452
				ms_message("NAT is symmetric for audio port");
smorlat's avatar
smorlat committed
453
			}
smorlat's avatar
smorlat committed
454
		}
455
		if (sock2!=-1){
smorlat's avatar
smorlat committed
456 457 458 459
			if (!got_video){
				ms_error("No stun server response for video port.");
			}else{
				if (!cone_video) {
460
					ms_message("NAT is symmetric for video port.");
smorlat's avatar
smorlat committed
461 462
				}
			}
smorlat's avatar
smorlat committed
463
		}
smorlat's avatar
smorlat committed
464
		close_socket(sock1);
465
		if (sock2!=-1) close_socket(sock2);
Simon Morlat's avatar
Simon Morlat committed
466
		return ret;
smorlat's avatar
smorlat committed
467
	}
Simon Morlat's avatar
Simon Morlat committed
468
	return -1;
smorlat's avatar
smorlat committed
469 470
}

471 472 473 474 475 476 477 478 479 480
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
481
void linphone_core_adapt_to_network(LinphoneCore *lc, int ping_time_ms, LinphoneCallParams *params){
Sylvain Berfini's avatar
Sylvain Berfini committed
482
	int threshold;
483
	if (ping_time_ms>0 && lp_config_get_int(lc->config,"net","activate_edge_workarounds",0)==1){
484
		ms_message("Stun server ping time is %i ms",ping_time_ms);
Sylvain Berfini's avatar
Sylvain Berfini committed
485
		threshold=lp_config_get_int(lc->config,"net","edge_ping_time",500);
Simon Morlat's avatar
Simon Morlat committed
486 487
		
		if (ping_time_ms>threshold){
488
			/* we might be in a 2G network*/
jehan's avatar
jehan committed
489
			params->low_bandwidth=TRUE;
Simon Morlat's avatar
Simon Morlat committed
490 491
		}/*else use default settings */
	}
492 493 494 495 496
	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
497 498
}

499 500 501 502 503 504 505 506 507 508 509
static void stun_server_resolved(LinphoneCore *lc, const char *name, struct addrinfo *addrinfo){
	if (lc->net_conf.stun_addrinfo){
		freeaddrinfo(lc->net_conf.stun_addrinfo);
		lc->net_conf.stun_addrinfo=NULL;
	}
	if (addrinfo){
		ms_message("Stun server resolution successful.");
	}else{
		ms_warning("Stun server resolution failed.");
	}
	lc->net_conf.stun_addrinfo=addrinfo;
Simon Morlat's avatar
Simon Morlat committed
510
	lc->net_conf.stun_res=NULL;
511
}
Simon Morlat's avatar
Simon Morlat committed
512

513
void linphone_core_resolve_stun_server(LinphoneCore *lc){
514 515 516 517
	/*
	 * WARNING: stun server resolution only done in IPv4.
	 * TODO: use IPv6 resolution if linphone_core_ipv6_enabled()==TRUE and use V4Mapped addresses for ICE gathering.
	 */
518 519 520 521 522
	const char *server=lc->net_conf.stun_server;
	if (lc->sal && server){
		char host[NI_MAXHOST];
		int port=3478;
		linphone_parse_host_port(server,host,sizeof(host),&port);
523
		lc->net_conf.stun_res=sal_resolve_a(lc->sal,host,port,AF_INET,(SalResolverCallback)stun_server_resolved,lc);
524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543
	}
}

/*
 * This function returns the addrinfo representation of the stun server address.
 * It is critical not to block for a long time if it can't be resolved, otherwise this stucks the main thread when making a call.
 * On the contrary, a fully asynchronous call initiation is complex to develop.
 * The compromise is then:
 * - have a cache of the stun server addrinfo
 * - this cached value is returned when it is non-null
 * - an asynchronous resolution is asked each time this function is called to ensure frequent refreshes of the cached value.
 * - if no cached value exists, block for a short time; this case must be unprobable because the resolution will be asked each time the stun server value is 
 * changed.
**/
const struct addrinfo *linphone_core_get_stun_server_addrinfo(LinphoneCore *lc){
	const char *server=linphone_core_get_stun_server(lc);
	if (server){
		int wait_ms=0;
		int wait_limit=1000;
		linphone_core_resolve_stun_server(lc);
Simon Morlat's avatar
Simon Morlat committed
544
		while (!lc->net_conf.stun_addrinfo && lc->net_conf.stun_res!=NULL && wait_ms<wait_limit){
545 546 547 548 549 550 551
			sal_iterate(lc->sal);
			ms_usleep(50000);
			wait_ms+=50;
		}
	}
	return lc->net_conf.stun_addrinfo;
}
Simon Morlat's avatar
Simon Morlat committed
552

553
int linphone_core_gather_ice_candidates(LinphoneCore *lc, LinphoneCall *call)
554
{
555
	char local_addr[64];
556
	const struct addrinfo *ai;
557 558
	IceCheckList *audio_check_list;
	IceCheckList *video_check_list;
559 560
	const char *server = linphone_core_get_stun_server(lc);

561 562 563
	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);
564
	if (audio_check_list == NULL) return -1;
565

566 567
	if (call->af==AF_INET6){
		ms_warning("Ice gathering is not implemented for ipv6");
568
		return -1;
569
	}
570 571 572
	ai=linphone_core_get_stun_server_addrinfo(lc);
	if (ai==NULL){
		ms_warning("Fail to resolve STUN server for ICE gathering.");
573
		return -1;
574 575 576 577
	}
	if (lc->vtable.display_status != NULL)
		lc->vtable.display_status(lc, _("ICE local candidates gathering in progress..."));

578
	/* Gather local host candidates. */
579
	if (linphone_core_get_local_ip_for(AF_INET, NULL, local_addr) < 0) {
580
		ms_error("Fail to get local ip");
581
		return -1;
582
	}
583 584 585 586 587 588 589
	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)) {
590 591
		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);
592
		call->stats[LINPHONE_CALL_STATS_VIDEO].ice_state = LinphoneIceStateInProgress;
593
	}
594

jehan's avatar
jehan committed
595
	ms_message("ICE: gathering candidate from [%s]",server);
596
	/* Gather local srflx candidates. */
597
	ice_session_gather_candidates(call->ice_session, ai->ai_addr, ai->ai_addrlen);
598
	return 0;
599 600
}

601 602 603 604
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
605
	IceSessionState session_state;
606 607 608 609 610 611

	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
612 613
	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))) {
614 615
		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
616
				case ICT_HostCandidate:
617
					call->stats[LINPHONE_CALL_STATS_AUDIO].ice_state = LinphoneIceStateHostConnection;
Ghislain MARY's avatar
Ghislain MARY committed
618 619 620
					break;
				case ICT_ServerReflexiveCandidate:
				case ICT_PeerReflexiveCandidate:
621
					call->stats[LINPHONE_CALL_STATS_AUDIO].ice_state = LinphoneIceStateReflexiveConnection;
Ghislain MARY's avatar
Ghislain MARY committed
622 623
					break;
				case ICT_RelayedCandidate:
624
					call->stats[LINPHONE_CALL_STATS_AUDIO].ice_state = LinphoneIceStateRelayConnection;
Ghislain MARY's avatar
Ghislain MARY committed
625 626
					break;
			}
627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646
		} 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
647
		}
648 649 650 651 652
	} 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
653 654 655 656 657
	} 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;
		}
658 659 660
	}
}

661 662
void linphone_core_update_local_media_description_from_ice(SalMediaDescription *desc, IceSession *session)
{
663
	const char *rtp_addr, *rtcp_addr;
664
	IceSessionState session_state = ice_session_state(session);
665
	int nb_candidates;
666
	int i, j;
667
	bool_t result;
668

669 670
	if (session_state == IS_Completed) {
		desc->ice_completed = TRUE;
Ghislain MARY's avatar
Ghislain MARY committed
671 672 673 674 675 676
		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!");
		}
677 678 679 680
	}
	else {
		desc->ice_completed = FALSE;
	}
681 682
	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));
683
	for (i = 0; i < desc->n_active_streams; i++) {
684 685
		SalStreamDescription *stream = &desc->streams[i];
		IceCheckList *cl = ice_session_check_list(session, i);
686
		nb_candidates = 0;
687
		if (cl == NULL) continue;
688
		if (ice_check_list_state(cl) == ICL_Completed) {
689
			stream->ice_completed = TRUE;
690 691 692 693 694 695
			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) {
696 697 698
			strncpy(stream->rtp_addr, rtp_addr, sizeof(stream->rtp_addr));
			strncpy(stream->rtcp_addr, rtcp_addr, sizeof(stream->rtcp_addr));
		} else {
699 700
			memset(stream->rtp_addr, 0, sizeof(stream->rtp_addr));
			memset(stream->rtcp_addr, 0, sizeof(stream->rtcp_addr));
701
		}
702 703 704 705 706 707 708 709
		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));
710
		stream->ice_mismatch = ice_check_list_is_mismatch(cl);
711
		if ((ice_check_list_state(cl) == ICL_Running) || (ice_check_list_state(cl) == ICL_Completed)) {
712
			memset(stream->ice_candidates, 0, sizeof(stream->ice_candidates));
Ghislain MARY's avatar
Ghislain MARY committed
713
			for (j = 0; j < MIN(ms_list_size(cl->local_candidates), SAL_MEDIA_DESCRIPTION_MAX_ICE_CANDIDATES); j++) {
714
				SalIceCandidate *sal_candidate = &stream->ice_candidates[nb_candidates];
715 716 717 718 719 720 721 722 723 724 725 726
				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. */
727
				if ((ice_check_list_state(cl) == ICL_Completed)
728 729 730 731 732 733 734 735 736 737 738 739
					&& !((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;
				}
740
				nb_candidates++;
741 742
			}
		}
743
		if ((ice_check_list_state(cl) == ICL_Completed) && (ice_session_role(session) == IR_Controlling)) {
744 745
			int rtp_port, rtcp_port;
			memset(stream->ice_remote_candidates, 0, sizeof(stream->ice_remote_candidates));
Ghislain MARY's avatar
Ghislain MARY committed
746
			if (ice_check_list_selected_valid_remote_candidate(cl, &rtp_addr, &rtp_port, &rtcp_addr, &rtcp_port) == TRUE) {
747 748 749 750 751 752 753
				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");
			}
754 755 756 757 758
		} 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;
			}
759 760 761 762
		}
	}
}

763 764 765 766 767 768 769 770 771 772 773 774
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;
}

775 776
void linphone_core_update_ice_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md)
{
777 778
	bool_t ice_restarted = FALSE;

779 780
	if ((md->ice_pwd[0] != '\0') && (md->ice_ufrag[0] != '\0')) {
		int i, j;
781 782

		/* Check for ICE restart and set remote credentials. */
783 784 785 786
		if ((strcmp(md->addr, "0.0.0.0") == 0) || (strcmp(md->addr, "::0") == 0)) {
			ice_session_restart(call->ice_session);
			ice_restarted = TRUE;
		} else {
787
			for (i = 0; i < md->n_total_streams; i++) {
788 789 790 791 792 793 794 795 796
				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;
				}
			}
		}
797 798 799
		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)) {
800 801 802 803
			if (ice_restarted == FALSE) {
				ice_session_restart(call->ice_session);
				ice_restarted = TRUE;
			}
804 805
			ice_session_set_remote_credentials(call->ice_session, md->ice_ufrag, md->ice_pwd);
		}
806
		for (i = 0; i < md->n_total_streams; i++) {
807 808 809 810
			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)) {
811 812 813 814
					if (ice_restarted == FALSE) {
						ice_session_restart(call->ice_session);
						ice_restarted = TRUE;
					}
815 816 817 818 819 820 821
					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. */
822
		for (i = 0; i < md->n_total_streams; i++) {
823 824
			const SalStreamDescription *stream = &md->streams[i];
			IceCheckList *cl = ice_session_check_list(call->ice_session, i);
825
			if ((cl == NULL) && (i < md->n_active_streams)) {
826 827 828 829
				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
830
						if (call->audiostream != NULL) call->audiostream->ms.ice_check_list = cl;
831 832
						break;
					case SalVideo:
Ghislain MARY's avatar
Ghislain MARY committed
833
						if (call->videostream != NULL) call->videostream->ms.ice_check_list = cl;
834 835 836 837 838
						break;
					default:
						break;
				}
			}
839
			if (stream->ice_mismatch == TRUE) {
840
				ice_check_list_set_state(cl, ICL_Failed);
841
			} else if (stream->rtp_port == 0) {
Ghislain MARY's avatar
Ghislain MARY committed
842
				ice_session_remove_check_list(call->ice_session, cl);
Simon Morlat's avatar
Simon Morlat committed
843 844 845 846 847 848
#ifdef VIDEO_ENABLED
				if (stream->type==SalVideo && call->videostream){
					video_stream_stop(call->videostream);
					call->videostream=NULL;
				}
#endif
849 850 851 852 853 854 855 856 857
			} 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;
858 859
					if ((candidate->componentID == 0) || (candidate->componentID > 2)) continue;
					get_default_addr_and_port(candidate->componentID, md, stream, &addr, &port);
860 861 862 863 864
					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);
				}
865
				if (ice_restarted == FALSE) {
866
					bool_t losing_pairs_added = FALSE;
867 868 869 870 871 872 873
					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);
874 875 876 877
						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);
						}
878 879
						ice_add_losing_pair(cl, j + 1, candidate->addr, candidate->port, addr, port);
						losing_pairs_added = TRUE;
880
					}
881
					if (losing_pairs_added == TRUE) ice_check_list_check_completed(cl);
882
				}
883 884
			}
		}
885
		for (i = ice_session_nb_check_lists(call->ice_session); i > md->n_active_streams; i--) {
886 887
			ice_session_remove_check_list(call->ice_session, ice_session_check_list(call->ice_session, i - 1));
		}
888
		ice_session_check_mismatch(call->ice_session);
889 890 891 892
	} else {
		/* Response from remote does not contain mandatory ICE attributes, delete the session. */
		linphone_call_delete_ice_session(call);
		return;
893
	}
894
	if (ice_session_nb_check_lists(call->ice_session) == 0) {
895 896 897 898
		linphone_call_delete_ice_session(call);
	}
}

899 900 901 902
bool_t linphone_core_media_description_contains_video_stream(const SalMediaDescription *md)
{
	int i;

903 904
	for (i = 0; i < md->n_active_streams; i++) {
		if (md->streams[i].type == SalVideo)
905 906 907 908 909
			return TRUE;
	}
	return FALSE;
}

910 911 912 913 914 915 916 917 918 919 920 921
LinphoneCall * is_a_linphone_call(void *user_pointer){
	LinphoneCall *call=(LinphoneCall*)user_pointer;
	if (call==NULL) return NULL;
	return call->magic==linphone_call_magic ? call : NULL;
}

LinphoneProxyConfig * is_a_linphone_proxy_config(void *user_pointer){
	LinphoneProxyConfig *cfg=(LinphoneProxyConfig*)user_pointer;
	if (cfg==NULL) return NULL;
	return cfg->magic==linphone_proxy_config_magic ? cfg : NULL;
}

922 923