upnp.c 36.7 KB
Newer Older
Yann Diorcet's avatar
Yann Diorcet committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/*
linphone
Copyright (C) 2012  Belledonne Communications SARL

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

Yann Diorcet's avatar
Yann Diorcet committed
20
#include "upnp.h"
Yann Diorcet's avatar
Yann Diorcet committed
21
#include "private.h"
22
#include "lpconfig.h"
Yann Diorcet's avatar
Yann Diorcet committed
23

24
#define UPNP_ADD_MAX_RETRY    4
Yann Diorcet's avatar
Yann Diorcet committed
25
#define UPNP_REMOVE_MAX_RETRY 4
26 27
#define UPNP_SECTION_NAME     "uPnP"
#define UPNP_CORE_READY_CHECK 1 
28 29
#define UPNP_CORE_RETRY_DELAY 4
#define UPNP_CALL_RETRY_DELAY 1
Yann Diorcet's avatar
Yann Diorcet committed
30

Yann Diorcet's avatar
Yann Diorcet committed
31 32 33 34 35 36
/*
 * uPnP Definitions
 */

typedef struct _UpnpPortBinding {
	ms_mutex_t mutex;
37
	LinphoneUpnpState state;
Yann Diorcet's avatar
Yann Diorcet committed
38 39 40 41 42 43 44
	upnp_igd_ip_protocol protocol;
	char local_addr[LINPHONE_IPADDR_SIZE];
	int local_port;
	char external_addr[LINPHONE_IPADDR_SIZE];
	int external_port;
	int retry;
	int ref;
45 46
	bool_t to_remove;
	bool_t to_add;
47
	time_t last_update;
Yann Diorcet's avatar
Yann Diorcet committed
48 49 50 51 52
} UpnpPortBinding;

typedef struct _UpnpStream {
	UpnpPortBinding *rtp;
	UpnpPortBinding *rtcp;
53
	LinphoneUpnpState state;
Yann Diorcet's avatar
Yann Diorcet committed
54 55 56 57 58 59
} UpnpStream;

struct _UpnpSession {
	LinphoneCall *call;
	UpnpStream *audio;
	UpnpStream *video;
60
	LinphoneUpnpState state;
Yann Diorcet's avatar
Yann Diorcet committed
61 62 63 64 65 66 67 68
};

struct _UpnpContext {
	LinphoneCore *lc;
	upnp_igd_context *upnp_igd_ctxt;
	UpnpPortBinding *sip_tcp;
	UpnpPortBinding *sip_tls;
	UpnpPortBinding *sip_udp;
69
	LinphoneUpnpState state;
Yann Diorcet's avatar
Yann Diorcet committed
70 71 72 73 74
	MSList *removing_configs;
	MSList *adding_configs;
	MSList *pending_bindings;

	ms_mutex_t mutex;
75
	ms_cond_t empty_cond;
76 77 78
	
	time_t last_ready_check;
	LinphoneUpnpState last_ready_state;
Yann Diorcet's avatar
Yann Diorcet committed
79 80 81
};


Yann Diorcet's avatar
Yann Diorcet committed
82
bool_t linphone_core_upnp_hook(void *data);
83
void linphone_core_upnp_refresh(UpnpContext *ctx);
Yann Diorcet's avatar
Yann Diorcet committed
84

85
UpnpPortBinding *linphone_upnp_port_binding_new();
86 87
UpnpPortBinding *linphone_upnp_port_binding_new_with_parameters(upnp_igd_ip_protocol protocol, int local_port, int external_port);
UpnpPortBinding *linphone_upnp_port_binding_new_or_collect(MSList *list, upnp_igd_ip_protocol protocol, int local_port, int external_port); 
88 89 90 91
UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port);
bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2);
UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port);
UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port);
92
void linphone_upnp_update_port_binding(UpnpContext *lupnp, UpnpPortBinding **port_mapping, upnp_igd_ip_protocol protocol, int port, int retry_delay); 
93 94
void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port);
void linphone_upnp_port_binding_release(UpnpPortBinding *port);
95
void linphone_upnp_update_config(UpnpContext *lupnp);
Yann Diorcet's avatar
Yann Diorcet committed
96
void linphone_upnp_update_proxy(UpnpContext *lupnp, bool_t force);
Yann Diorcet's avatar
Yann Diorcet committed
97

98
// Configuration
99 100 101
MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc);
void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port);
void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port);
Yann Diorcet's avatar
Yann Diorcet committed
102

103 104 105
// uPnP 
int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry);
int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry);
Yann Diorcet's avatar
Yann Diorcet committed
106

Yann Diorcet's avatar
Yann Diorcet committed
107

Yann Diorcet's avatar
Yann Diorcet committed
108 109 110 111
/**
 * uPnP Callbacks
 */

Yann Diorcet's avatar
Yann Diorcet committed
112 113 114 115 116 117 118 119
/* Convert uPnP IGD logs to ortp logs */
void linphone_upnp_igd_print(void *cookie, upnp_igd_print_level level, const char *fmt, va_list list) {
	int ortp_level = ORTP_DEBUG;
	switch(level) {
	case UPNP_IGD_MESSAGE:
		ortp_level = ORTP_MESSAGE;
		break;
	case UPNP_IGD_WARNING:
Yann Diorcet's avatar
Yann Diorcet committed
120
		ortp_level = ORTP_DEBUG; // Too verbose otherwise
Yann Diorcet's avatar
Yann Diorcet committed
121 122
		break;
	case UPNP_IGD_ERROR:
Yann Diorcet's avatar
Yann Diorcet committed
123
		ortp_level = ORTP_DEBUG; // Too verbose otherwise
Yann Diorcet's avatar
Yann Diorcet committed
124 125 126 127
		break;
	default:
		break;
	}
Yann Diorcet's avatar
Yann Diorcet committed
128
	ortp_logv(ortp_level, fmt, list);
Yann Diorcet's avatar
Yann Diorcet committed
129 130 131
}

void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) {
Yann Diorcet's avatar
Yann Diorcet committed
132
	UpnpContext *lupnp = (UpnpContext *)cookie;
Yann Diorcet's avatar
Yann Diorcet committed
133 134 135 136 137
	upnp_igd_port_mapping *mapping = NULL;
	UpnpPortBinding *port_mapping = NULL;
	const char *ip_address = NULL;
	const char *connection_status = NULL;
	bool_t nat_enabled = FALSE;
138 139 140 141 142 143 144
	LinphoneUpnpState old_state;

	if(lupnp == NULL || lupnp->upnp_igd_ctxt == NULL) {
		ms_error("uPnP IGD: Invalid context in callback");
		return;
	}

Yann Diorcet's avatar
Yann Diorcet committed
145
	ms_mutex_lock(&lupnp->mutex);
146
	old_state = lupnp->state;
Yann Diorcet's avatar
Yann Diorcet committed
147

Yann Diorcet's avatar
Yann Diorcet committed
148
	switch(event) {
149 150
	case UPNP_IGD_DEVICE_ADDED:
	case UPNP_IGD_DEVICE_REMOVED:
Yann Diorcet's avatar
Yann Diorcet committed
151 152 153
	case UPNP_IGD_EXTERNAL_IPADDRESS_CHANGED:
	case UPNP_IGD_NAT_ENABLED_CHANGED:
	case UPNP_IGD_CONNECTION_STATUS_CHANGED:
Yann Diorcet's avatar
Yann Diorcet committed
154 155 156 157 158
		ip_address = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
		connection_status = upnp_igd_get_connection_status(lupnp->upnp_igd_ctxt);
		nat_enabled = upnp_igd_get_nat_enabled(lupnp->upnp_igd_ctxt);

		if(ip_address == NULL || connection_status == NULL) {
Yann Diorcet's avatar
Yann Diorcet committed
159 160
			ms_message("uPnP IGD: Pending");
			lupnp->state = LinphoneUpnpStatePending;
Yann Diorcet's avatar
Yann Diorcet committed
161
		} else if(strcasecmp(connection_status, "Connected")  || !nat_enabled) {
Yann Diorcet's avatar
Yann Diorcet committed
162 163
			ms_message("uPnP IGD: Not Available");
			lupnp->state = LinphoneUpnpStateNotAvailable;
Yann Diorcet's avatar
Yann Diorcet committed
164
		} else {
Yann Diorcet's avatar
Yann Diorcet committed
165 166
			ms_message("uPnP IGD: Connected");
			lupnp->state = LinphoneUpnpStateOk;
167 168 169
			if(old_state != LinphoneUpnpStateOk) {
				linphone_core_upnp_refresh(lupnp);
			}
Yann Diorcet's avatar
Yann Diorcet committed
170 171 172 173 174 175 176
		}

		break;

	case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS:
		mapping = (upnp_igd_port_mapping *) arg;
		port_mapping = (UpnpPortBinding*) mapping->cookie;
Yann Diorcet's avatar
Yann Diorcet committed
177 178
		port_mapping->external_port = mapping->remote_port;
		port_mapping->state = LinphoneUpnpStateOk;
179 180
		linphone_upnp_port_binding_log(ORTP_MESSAGE, "Added port binding", port_mapping);
		linphone_upnp_config_add_port_binding(lupnp, port_mapping);
Yann Diorcet's avatar
Yann Diorcet committed
181

Yann Diorcet's avatar
Yann Diorcet committed
182 183 184 185 186
		break;

	case UPNP_IGD_PORT_MAPPING_ADD_FAILURE:
		mapping = (upnp_igd_port_mapping *) arg;
		port_mapping = (UpnpPortBinding*) mapping->cookie;
Yann Diorcet's avatar
Yann Diorcet committed
187
		port_mapping->external_port = -1; //Force random external port
188
		if(linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, TRUE) != 0) {
189
			linphone_upnp_port_binding_log(ORTP_ERROR, "Can't add port binding", port_mapping);
Yann Diorcet's avatar
Yann Diorcet committed
190 191
		}

Yann Diorcet's avatar
Yann Diorcet committed
192 193 194 195 196
		break;

	case UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS:
		mapping = (upnp_igd_port_mapping *) arg;
		port_mapping = (UpnpPortBinding*) mapping->cookie;
Yann Diorcet's avatar
Yann Diorcet committed
197
		port_mapping->state = LinphoneUpnpStateIdle;
198 199
		linphone_upnp_port_binding_log(ORTP_MESSAGE, "Removed port binding", port_mapping);
		linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
Yann Diorcet's avatar
Yann Diorcet committed
200

Yann Diorcet's avatar
Yann Diorcet committed
201 202 203 204 205
		break;

	case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE:
		mapping = (upnp_igd_port_mapping *) arg;
		port_mapping = (UpnpPortBinding*) mapping->cookie;
206
		if(linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE) != 0) {
207 208
			linphone_upnp_port_binding_log(ORTP_ERROR, "Can't remove port binding", port_mapping);
			linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
Yann Diorcet's avatar
Yann Diorcet committed
209 210
		}

Yann Diorcet's avatar
Yann Diorcet committed
211 212 213 214 215
		break;

	default:
		break;
	}
Yann Diorcet's avatar
Yann Diorcet committed
216

217 218 219 220 221 222 223
	if(port_mapping != NULL) {
		/*
		 * Execute delayed actions
		 */
		if(port_mapping->to_remove) {
			if(port_mapping->state == LinphoneUpnpStateOk) {
				port_mapping->to_remove = FALSE;
224
				linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, FALSE);
225 226 227
			} else if(port_mapping->state == LinphoneUpnpStateKo) {
				port_mapping->to_remove = FALSE;
			}
Yann Diorcet's avatar
Yann Diorcet committed
228
		}
229 230 231
		if(port_mapping->to_add) {
			if(port_mapping->state == LinphoneUpnpStateIdle || port_mapping->state == LinphoneUpnpStateKo) {
				port_mapping->to_add = FALSE;
232
				linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, FALSE);
233 234 235 236 237 238 239 240 241 242 243 244
			}
		}

		lupnp->pending_bindings = ms_list_remove(lupnp->pending_bindings, port_mapping);
		linphone_upnp_port_binding_release(port_mapping);
	}

	/*
	 * If there is no pending binding emit a signal
	 */
	if(lupnp->pending_bindings == NULL) {
		pthread_cond_signal(&lupnp->empty_cond);
Yann Diorcet's avatar
Yann Diorcet committed
245
	}
Yann Diorcet's avatar
Yann Diorcet committed
246 247 248
	ms_mutex_unlock(&lupnp->mutex);
}

Yann Diorcet's avatar
Yann Diorcet committed
249 250 251 252 253

/**
 * uPnP Context
 */

254
UpnpContext* linphone_upnp_context_new(LinphoneCore *lc) {
Yann Diorcet's avatar
Yann Diorcet committed
255
	UpnpContext *lupnp = (UpnpContext *)ms_new0(UpnpContext,1);
Yann Diorcet's avatar
Yann Diorcet committed
256 257

	ms_mutex_init(&lupnp->mutex, NULL);
258
	ms_cond_init(&lupnp->empty_cond, NULL);
Yann Diorcet's avatar
Yann Diorcet committed
259

260 261 262
	lupnp->last_ready_check = 0;
	lupnp->last_ready_state = LinphoneUpnpStateIdle;

Yann Diorcet's avatar
Yann Diorcet committed
263
	lupnp->lc = lc;
Yann Diorcet's avatar
Yann Diorcet committed
264 265 266
	lupnp->pending_bindings = NULL;
	lupnp->adding_configs = NULL;
	lupnp->removing_configs = NULL;
Yann Diorcet's avatar
Yann Diorcet committed
267
	lupnp->state = LinphoneUpnpStateIdle;
Yann Diorcet's avatar
Yann Diorcet committed
268
	ms_message("uPnP IGD: New %p for core %p", lupnp, lc);
Yann Diorcet's avatar
Yann Diorcet committed
269

270 271 272 273
	// Init ports
	lupnp->sip_udp = NULL;
	lupnp->sip_tcp = NULL;
	lupnp->sip_tls = NULL;
Yann Diorcet's avatar
Yann Diorcet committed
274

Yann Diorcet's avatar
Yann Diorcet committed
275
	linphone_core_add_iterate_hook(lc, linphone_core_upnp_hook, lupnp);
Yann Diorcet's avatar
Yann Diorcet committed
276 277

	lupnp->upnp_igd_ctxt = NULL;
Yann Diorcet's avatar
Yann Diorcet committed
278
	lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, lupnp);
Yann Diorcet's avatar
Yann Diorcet committed
279 280 281
	if(lupnp->upnp_igd_ctxt == NULL) {
		lupnp->state = LinphoneUpnpStateKo;
		ms_error("Can't create uPnP IGD context");
Yann Diorcet's avatar
Yann Diorcet committed
282
		return NULL;
Yann Diorcet's avatar
Yann Diorcet committed
283 284 285
	}

	lupnp->state = LinphoneUpnpStatePending;
286 287
	upnp_igd_start(lupnp->upnp_igd_ctxt);

Yann Diorcet's avatar
Yann Diorcet committed
288
	return lupnp;
Yann Diorcet's avatar
Yann Diorcet committed
289 290
}

291
void linphone_upnp_context_destroy(UpnpContext *lupnp) {
292
	linphone_core_remove_iterate_hook(lupnp->lc, linphone_core_upnp_hook, lupnp);
Yann Diorcet's avatar
Yann Diorcet committed
293

294 295
	ms_mutex_lock(&lupnp->mutex);
	
Yann Diorcet's avatar
Yann Diorcet committed
296
	/* Send port binding removes */
Yann Diorcet's avatar
Yann Diorcet committed
297
	if(lupnp->sip_udp != NULL) {
298
		linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_udp, TRUE);
Yann Diorcet's avatar
Yann Diorcet committed
299 300
	}
	if(lupnp->sip_tcp != NULL) {
301
		linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tcp, TRUE);
Yann Diorcet's avatar
Yann Diorcet committed
302 303
	}
	if(lupnp->sip_tls != NULL) {
304
		linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tls, TRUE);
Yann Diorcet's avatar
Yann Diorcet committed
305
	}
Yann Diorcet's avatar
Yann Diorcet committed
306 307

	/* Wait all pending bindings are done */
308 309 310 311
	if(lupnp->pending_bindings != NULL) {
		ms_message("uPnP IGD: Wait all pending port bindings ...");
		ms_cond_wait(&lupnp->empty_cond, &lupnp->mutex);
	}
Yann Diorcet's avatar
Yann Diorcet committed
312

Yann Diorcet's avatar
Yann Diorcet committed
313
	if(lupnp->upnp_igd_ctxt != NULL) {
Yann Diorcet's avatar
Yann Diorcet committed
314 315 316
		// upnp_igd_destroy is synchronous so the callbacks will be called in the same thread.
		// So release the mutex before upnp_igd_destroy call. 
		ms_mutex_unlock(&lupnp->mutex);
Yann Diorcet's avatar
Yann Diorcet committed
317
		upnp_igd_destroy(lupnp->upnp_igd_ctxt);
Yann Diorcet's avatar
Yann Diorcet committed
318
		ms_mutex_lock(&lupnp->mutex);
319
		lupnp->upnp_igd_ctxt = NULL;
Yann Diorcet's avatar
Yann Diorcet committed
320
	}
Yann Diorcet's avatar
Yann Diorcet committed
321

Yann Diorcet's avatar
Yann Diorcet committed
322
	/* Run one more time configuration update and proxy */
323
	linphone_upnp_update_config(lupnp);
Yann Diorcet's avatar
Yann Diorcet committed
324
	linphone_upnp_update_proxy(lupnp, TRUE);
Yann Diorcet's avatar
Yann Diorcet committed
325 326 327

	/* Release port bindings */
	if(lupnp->sip_udp != NULL) {
328
		linphone_upnp_port_binding_release(lupnp->sip_udp);
Yann Diorcet's avatar
Yann Diorcet committed
329 330 331
		lupnp->sip_udp = NULL;
	}
	if(lupnp->sip_tcp != NULL) {
332
		linphone_upnp_port_binding_release(lupnp->sip_tcp);
Yann Diorcet's avatar
Yann Diorcet committed
333 334 335
		lupnp->sip_tcp = NULL;
	}
	if(lupnp->sip_tls != NULL) {
336
		linphone_upnp_port_binding_release(lupnp->sip_tls);
Yann Diorcet's avatar
Yann Diorcet committed
337 338 339 340
		lupnp->sip_tcp = NULL;
	}

	/* Release lists */
341
	ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
Yann Diorcet's avatar
Yann Diorcet committed
342
	lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
343
	ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
Yann Diorcet's avatar
Yann Diorcet committed
344
	lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
345
	ms_list_for_each(lupnp->pending_bindings,(void (*)(void*))linphone_upnp_port_binding_release);
Yann Diorcet's avatar
Yann Diorcet committed
346
	lupnp->pending_bindings = ms_list_free(lupnp->pending_bindings);
347 348
	
	ms_mutex_unlock(&lupnp->mutex);
Yann Diorcet's avatar
Yann Diorcet committed
349

Yann Diorcet's avatar
Yann Diorcet committed
350
	ms_mutex_destroy(&lupnp->mutex);
351
	ms_cond_destroy(&lupnp->empty_cond);
Yann Diorcet's avatar
Yann Diorcet committed
352

Yann Diorcet's avatar
Yann Diorcet committed
353 354
	ms_message("uPnP IGD: destroy %p", lupnp);
	ms_free(lupnp);
Yann Diorcet's avatar
Yann Diorcet committed
355 356
}

357 358 359 360 361 362 363 364
LinphoneUpnpState linphone_upnp_context_get_state(UpnpContext *lupnp) {
	LinphoneUpnpState state;
	ms_mutex_lock(&lupnp->mutex);
	state = lupnp->state;
	ms_mutex_unlock(&lupnp->mutex);
	return state;
}

365
bool_t _linphone_upnp_context_is_ready_for_register(UpnpContext *lupnp) {
366
	bool_t ready = TRUE;
367
	
368 369 370 371
	// 1 Check global uPnP state
	ready = (lupnp->state == LinphoneUpnpStateOk);
	
	// 2 Check external ip address
372 373 374 375
	if(ready) {
		if (upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt) == NULL) {
			ready = FALSE;
		}
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
	}
	
	// 3 Check sip ports bindings
	if(ready) {
		if(lupnp->sip_udp != NULL) {
			if(lupnp->sip_udp->state != LinphoneUpnpStateOk) {
				ready = FALSE;
			}
		} else if(lupnp->sip_tcp != NULL) {
			if(lupnp->sip_tcp->state != LinphoneUpnpStateOk) {
				ready = FALSE;
			}
		} else if(lupnp->sip_tls != NULL) {
			if(lupnp->sip_tls->state != LinphoneUpnpStateOk) {
				ready = FALSE;
			}
		} else {
			ready = FALSE;
		}
	}
396 397 398
	
	return ready;
}
399

400 401 402 403
bool_t linphone_upnp_context_is_ready_for_register(UpnpContext *lupnp) {
	bool_t ready;
	ms_mutex_lock(&lupnp->mutex);
	ready = _linphone_upnp_context_is_ready_for_register(lupnp);
404 405 406 407
	ms_mutex_unlock(&lupnp->mutex);
	return ready;
}

408 409 410 411 412
int linphone_upnp_context_get_external_port(UpnpContext *lupnp) {
	int port = -1;
	ms_mutex_lock(&lupnp->mutex);
	
	if(lupnp->sip_udp != NULL) {
413
		if(lupnp->sip_udp->state == LinphoneUpnpStateOk) {
414
			port = lupnp->sip_udp->external_port;
415
		}
416
	} else if(lupnp->sip_tcp != NULL) {
417
		if(lupnp->sip_tcp->state == LinphoneUpnpStateOk) {
418
			port = lupnp->sip_tcp->external_port;
419
		}
420
	} else if(lupnp->sip_tls != NULL) {
421
		if(lupnp->sip_tls->state == LinphoneUpnpStateOk) {
422
			port = lupnp->sip_tls->external_port;
423
		}
424 425 426 427
	}
	
	ms_mutex_unlock(&lupnp->mutex);
	return port;
Yann Diorcet's avatar
Yann Diorcet committed
428 429
}

430 431 432 433 434 435
const char* linphone_upnp_context_get_external_ipaddress(UpnpContext *lupnp) {
	const char* addr = NULL;
	ms_mutex_lock(&lupnp->mutex);
	addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
	ms_mutex_unlock(&lupnp->mutex);
	return addr;
Yann Diorcet's avatar
Yann Diorcet committed
436 437
}

438
int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry) {
Yann Diorcet's avatar
Yann Diorcet committed
439
	upnp_igd_port_mapping mapping;
Yann Diorcet's avatar
Yann Diorcet committed
440
	char description[128];
Yann Diorcet's avatar
Yann Diorcet committed
441
	int ret;
442 443 444 445
	
	if(lupnp->state != LinphoneUpnpStateOk) {
		return -2;
	}
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464

	// Compute port binding state
	if(port->state != LinphoneUpnpStateAdding) {
		port->to_remove = FALSE;
		switch(port->state) {
			case LinphoneUpnpStateKo:
			case LinphoneUpnpStateIdle: {
				port->retry = 0;
				port->state = LinphoneUpnpStateAdding;
			}
			break;
			case LinphoneUpnpStateRemoving: {
				port->to_add = TRUE;
				return 0;
			}
			break;
			default:
				return 0;
		}
Yann Diorcet's avatar
Yann Diorcet committed
465
	}
466 467 468 469 470
	
	// No retry if specified
	if(port->retry != 0 && !retry) {
		return -1;
	}
Yann Diorcet's avatar
Yann Diorcet committed
471

Yann Diorcet's avatar
Yann Diorcet committed
472
	if(port->retry >= UPNP_ADD_MAX_RETRY) {
Yann Diorcet's avatar
Yann Diorcet committed
473 474
		ret = -1;
	} else {
475
		mapping.cookie = linphone_upnp_port_binding_retain(port);
Yann Diorcet's avatar
Yann Diorcet committed
476 477
		lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);

Yann Diorcet's avatar
Yann Diorcet committed
478
		mapping.local_port = port->local_port;
Yann Diorcet's avatar
Yann Diorcet committed
479 480
		mapping.local_host = port->local_addr;
		if(port->external_port == -1)
Yann Diorcet's avatar
Yann Diorcet committed
481
			mapping.remote_port = rand()%(0xffff - 1024) + 1024;
Yann Diorcet's avatar
Yann Diorcet committed
482 483
		else
			mapping.remote_port = port->external_port;
Yann Diorcet's avatar
Yann Diorcet committed
484
		mapping.remote_host = "";
Yann Diorcet's avatar
Yann Diorcet committed
485 486 487 488 489
		snprintf(description, 128, "%s %s at %s:%d",
				PACKAGE_NAME,
				(port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP": "UDP",
				port->local_addr, port->local_port);
		mapping.description = description;
Yann Diorcet's avatar
Yann Diorcet committed
490 491 492
		mapping.protocol = port->protocol;

		port->retry++;
493
		linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to add port binding", port);
Yann Diorcet's avatar
Yann Diorcet committed
494 495 496
		ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
	}
	if(ret != 0) {
Yann Diorcet's avatar
Yann Diorcet committed
497
		port->state = LinphoneUpnpStateKo;
Yann Diorcet's avatar
Yann Diorcet committed
498 499 500 501
	}
	return ret;
}

502
int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry) {
Yann Diorcet's avatar
Yann Diorcet committed
503 504
	upnp_igd_port_mapping mapping;
	int ret;
505 506 507 508
	
	if(lupnp->state != LinphoneUpnpStateOk) {
		return -2;
	}
509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526

	// Compute port binding state
	if(port->state != LinphoneUpnpStateRemoving) {
		port->to_add = FALSE;
		switch(port->state) {
			case LinphoneUpnpStateOk: {
				port->retry = 0;
				port->state = LinphoneUpnpStateRemoving;
			}
			break;
			case LinphoneUpnpStateAdding: {
				port->to_remove = TRUE;
				return 0;
			}
			break;
			default:
				return 0;
		}
Yann Diorcet's avatar
Yann Diorcet committed
527
	}
528 529 530 531 532
	
	// No retry if specified
	if(port->retry != 0 && !retry) {
		return 1;
	}
Yann Diorcet's avatar
Yann Diorcet committed
533

Yann Diorcet's avatar
Yann Diorcet committed
534
	if(port->retry >= UPNP_REMOVE_MAX_RETRY) {
Yann Diorcet's avatar
Yann Diorcet committed
535 536
		ret = -1;
	} else {
537
		mapping.cookie = linphone_upnp_port_binding_retain(port);
Yann Diorcet's avatar
Yann Diorcet committed
538 539
		lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);

Yann Diorcet's avatar
Yann Diorcet committed
540
		mapping.remote_port = port->external_port;
Yann Diorcet's avatar
Yann Diorcet committed
541 542 543
		mapping.remote_host = "";
		mapping.protocol = port->protocol;
		port->retry++;
544
		linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to remove port binding", port);
Yann Diorcet's avatar
Yann Diorcet committed
545 546 547
		ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
	}
	if(ret != 0) {
Yann Diorcet's avatar
Yann Diorcet committed
548
		port->state = LinphoneUpnpStateKo;
Yann Diorcet's avatar
Yann Diorcet committed
549 550 551 552
	}
	return ret;
}

Yann Diorcet's avatar
Yann Diorcet committed
553 554 555 556
/*
 * uPnP Core interfaces
 */

Yann Diorcet's avatar
Yann Diorcet committed
557
int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool_t video) {
Yann Diorcet's avatar
Yann Diorcet committed
558
	LinphoneCore *lc = call->core;
Yann Diorcet's avatar
Yann Diorcet committed
559
	UpnpContext *lupnp = lc->upnp;
Yann Diorcet's avatar
Yann Diorcet committed
560 561
	int ret = -1;

Yann Diorcet's avatar
Yann Diorcet committed
562 563 564 565
	if(lupnp == NULL) {
		return ret;
	}

Yann Diorcet's avatar
Yann Diorcet committed
566
	ms_mutex_lock(&lupnp->mutex);
567

Yann Diorcet's avatar
Yann Diorcet committed
568
	// Don't handle when the call
Yann Diorcet's avatar
Yann Diorcet committed
569
	if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
Yann Diorcet's avatar
Yann Diorcet committed
570 571 572 573 574
		ret = 0;

		/*
		 * Audio part
		 */
575 576
		linphone_upnp_update_port_binding(lupnp, &call->upnp_session->audio->rtp, 
			UPNP_IGD_IP_PROTOCOL_UDP, (audio)? call->audio_port:0, UPNP_CALL_RETRY_DELAY);
Yann Diorcet's avatar
Yann Diorcet committed
577

578 579 580
		linphone_upnp_update_port_binding(lupnp, &call->upnp_session->audio->rtcp, 
			UPNP_IGD_IP_PROTOCOL_UDP, (audio)? call->audio_port+1:0, UPNP_CALL_RETRY_DELAY);
		
Yann Diorcet's avatar
Yann Diorcet committed
581 582 583
		/*
		 * Video part
		 */
584 585 586 587 588
		linphone_upnp_update_port_binding(lupnp, &call->upnp_session->video->rtp, 
			UPNP_IGD_IP_PROTOCOL_UDP, (video)? call->video_port:0, UPNP_CALL_RETRY_DELAY);

		linphone_upnp_update_port_binding(lupnp, &call->upnp_session->video->rtcp, 
			UPNP_IGD_IP_PROTOCOL_UDP, (video)? call->video_port+1:0, UPNP_CALL_RETRY_DELAY);
Yann Diorcet's avatar
Yann Diorcet committed
589 590 591
	}

	ms_mutex_unlock(&lupnp->mutex);
Yann Diorcet's avatar
Yann Diorcet committed
592 593 594 595

	/*
	 * Update uPnP call state
	 */
596
	linphone_upnp_call_process(call);
Yann Diorcet's avatar
Yann Diorcet committed
597

Yann Diorcet's avatar
Yann Diorcet committed
598 599 600 601
	return ret;
}


602

Yann Diorcet's avatar
Yann Diorcet committed
603 604 605 606 607 608
int linphone_core_update_upnp_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) {
	bool_t audio = FALSE;
	bool_t video = FALSE;
	int i;
	const SalStreamDescription *stream;

Yann Diorcet's avatar
Yann Diorcet committed
609
	for (i = 0; i < md->n_total_streams; i++) {
Yann Diorcet's avatar
Yann Diorcet committed
610 611 612 613 614 615 616 617 618 619 620 621 622 623 624
		stream = &md->streams[i];
		if(stream->type == SalAudio) {
			audio = TRUE;
		} else if(stream->type == SalVideo) {
			video = TRUE;
		}
	}

	return linphone_core_update_upnp_audio_video(call, audio, video);
}

int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
	return linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
}

Yann Diorcet's avatar
Yann Diorcet committed
625 626 627 628 629
void linphone_core_update_upnp_state_in_call_stats(LinphoneCall *call) {
	call->stats[LINPHONE_CALL_STATS_AUDIO].upnp_state = call->upnp_session->audio->state;
	call->stats[LINPHONE_CALL_STATS_VIDEO].upnp_state = call->upnp_session->video->state;
}

630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646
void linphone_upnp_update_stream_state(UpnpStream *stream) {
	if((stream->rtp == NULL || stream->rtp->state == LinphoneUpnpStateOk || stream->rtp->state == LinphoneUpnpStateIdle) &&
	   (stream->rtcp == NULL || stream->rtcp->state == LinphoneUpnpStateOk || stream->rtcp->state == LinphoneUpnpStateIdle)) {
		stream->state = LinphoneUpnpStateOk;
	} else if((stream->rtp != NULL && 
                     (stream->rtp->state == LinphoneUpnpStateAdding || stream->rtp->state == LinphoneUpnpStateRemoving)) ||
		  (stream->rtcp != NULL && 
		     (stream->rtcp->state == LinphoneUpnpStateAdding || stream->rtcp->state == LinphoneUpnpStateRemoving))) {
		stream->state = LinphoneUpnpStatePending;
	} else if((stream->rtp != NULL && stream->rtp->state == LinphoneUpnpStateKo) ||
			(stream->rtcp != NULL && stream->rtcp->state == LinphoneUpnpStateKo)) {
		stream->state = LinphoneUpnpStateKo;
	} else {
		ms_error("Invalid stream %p state", stream);		
	}
}

647
int linphone_upnp_call_process(LinphoneCall *call) {
Yann Diorcet's avatar
Yann Diorcet committed
648
	LinphoneCore *lc = call->core;
Yann Diorcet's avatar
Yann Diorcet committed
649
	UpnpContext *lupnp = lc->upnp;
Yann Diorcet's avatar
Yann Diorcet committed
650
	int ret = -1;
Yann Diorcet's avatar
Yann Diorcet committed
651
	LinphoneUpnpState oldState = 0, newState = 0;
Yann Diorcet's avatar
Yann Diorcet committed
652

Yann Diorcet's avatar
Yann Diorcet committed
653 654 655 656
	if(lupnp == NULL) {
		return ret;
	}

Yann Diorcet's avatar
Yann Diorcet committed
657 658 659 660 661 662 663 664 665
	ms_mutex_lock(&lupnp->mutex);

	// Don't handle when the call
	if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
		ret = 0;

		/*
		 * Update Audio state
		 */
666
		linphone_upnp_update_stream_state(call->upnp_session->audio);
Yann Diorcet's avatar
Yann Diorcet committed
667 668 669 670

		/*
		 * Update Video state
		 */
671
		linphone_upnp_update_stream_state(call->upnp_session->video);
Yann Diorcet's avatar
Yann Diorcet committed
672

Yann Diorcet's avatar
Yann Diorcet committed
673 674 675 676 677
		/*
		 * Update stat
		 */
		linphone_core_update_upnp_state_in_call_stats(call);
		
Yann Diorcet's avatar
Yann Diorcet committed
678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693
		/*
		 * Update session state
		 */
		oldState = call->upnp_session->state;
		if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
			call->upnp_session->video->state == LinphoneUpnpStateOk) {
			call->upnp_session->state = LinphoneUpnpStateOk;
		} else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
				call->upnp_session->video->state == LinphoneUpnpStatePending) {
			call->upnp_session->state = LinphoneUpnpStatePending;
		} else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
				call->upnp_session->video->state == LinphoneUpnpStateKo) {
			call->upnp_session->state = LinphoneUpnpStateKo;
		} else {
			call->upnp_session->state = LinphoneUpnpStateIdle;
		}
Yann Diorcet's avatar
Yann Diorcet committed
694
		newState = call->upnp_session->state;
Yann Diorcet's avatar
Yann Diorcet committed
695
	}
Yann Diorcet's avatar
Yann Diorcet committed
696

Yann Diorcet's avatar
Yann Diorcet committed
697
	ms_mutex_unlock(&lupnp->mutex);
Yann Diorcet's avatar
Yann Diorcet committed
698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722
	
	/* When change is done proceed update */
	if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
			(newState == LinphoneUpnpStateOk || newState == LinphoneUpnpStateKo)) {
		if(call->upnp_session->state == LinphoneUpnpStateOk)
			ms_message("uPnP IGD: uPnP for Call %p is ok", call);
		else
			ms_message("uPnP IGD: uPnP for Call %p is ko", call);

		switch (call->state) {
			case LinphoneCallUpdating:
				linphone_core_start_update_call(lc, call);
				break;
			case LinphoneCallUpdatedByRemote:
				linphone_core_start_accept_call_update(lc, call);
				break;
			case LinphoneCallOutgoingInit:
				linphone_core_proceed_with_invite_if_ready(lc, call, NULL);
				break;
			case LinphoneCallIdle:
				linphone_core_notify_incoming_call(lc, call);
				break;
			default:
				break;
		}
Yann Diorcet's avatar
Yann Diorcet committed
723 724
	}

Yann Diorcet's avatar
Yann Diorcet committed
725 726 727
	return ret;
}

728 729
void linphone_core_upnp_refresh(UpnpContext *lupnp) {
	MSList *global_list = NULL;
Yann Diorcet's avatar
Yann Diorcet committed
730 731 732
	MSList *list = NULL;
	MSList *item;
	LinphoneCall *call;
733
	UpnpPortBinding *port_mapping, *port_mapping2;
Yann Diorcet's avatar
Yann Diorcet committed
734

735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750
	ms_message("uPnP IGD: Refresh mappings");

	if(lupnp->sip_udp != NULL) {
		global_list = ms_list_append(global_list, lupnp->sip_udp);
	}
	if(lupnp->sip_tcp != NULL) {
		global_list = ms_list_append(global_list, lupnp->sip_tcp);
	}
	if(lupnp->sip_tls != NULL) {
		global_list = ms_list_append(global_list, lupnp->sip_tls);
	}

	list = lupnp->lc->calls;
	while(list != NULL) {
		call = (LinphoneCall *)list->data;
		if(call->upnp_session != NULL) {
751 752 753 754 755 756 757 758 759 760 761 762
			if(call->upnp_session->audio->rtp != NULL) {
				global_list = ms_list_append(global_list, call->upnp_session->audio->rtp);
			}
			if(call->upnp_session->audio->rtcp != NULL) {
				global_list = ms_list_append(global_list, call->upnp_session->audio->rtcp);
			}
			if(call->upnp_session->video->rtp != NULL) {
				global_list = ms_list_append(global_list, call->upnp_session->video->rtp);
			}
			if(call->upnp_session->video->rtcp != NULL) {
				global_list = ms_list_append(global_list, call->upnp_session->video->rtcp);
			}
Yann Diorcet's avatar
Yann Diorcet committed
763
		}
764 765 766 767 768 769 770 771
		list = list->next;
	}

	list = linphone_upnp_config_list_port_bindings(lupnp->lc->config);
	for(item = list;item != NULL; item = item->next) {
			port_mapping = (UpnpPortBinding *)item->data;
			port_mapping2 = linphone_upnp_port_binding_equivalent_in_list(global_list, port_mapping);
			if(port_mapping2 == NULL) {
772
				linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE);
773 774 775 776
			} else if(port_mapping2->state == LinphoneUpnpStateIdle){
				/* Force to remove */
				port_mapping2->state = LinphoneUpnpStateOk;
			}
Yann Diorcet's avatar
Yann Diorcet committed
777
	}
778 779
	ms_list_for_each(list, (void (*)(void*))linphone_upnp_port_binding_release);
	list = ms_list_free(list);
Yann Diorcet's avatar
Yann Diorcet committed
780

Yann Diorcet's avatar
Yann Diorcet committed
781

782 783 784 785
	// (Re)Add removed port bindings
	list = global_list;
	while(list != NULL) {
		port_mapping = (UpnpPortBinding *)list->data;
786 787
		linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE);
		linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, TRUE);
788
		list = list->next;
Yann Diorcet's avatar
Yann Diorcet committed
789
	}
790 791 792
	global_list = ms_list_free(global_list);
}

793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809
void linphone_upnp_update_port_binding(UpnpContext *lupnp, UpnpPortBinding **port_mapping, upnp_igd_ip_protocol protocol, int port, int retry_delay) {
	const char *local_addr, *external_addr;
	time_t now = time(NULL);
	if(port != 0) {
		if(*port_mapping != NULL) {
			if(port != (*port_mapping)->local_port) {
				linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
				*port_mapping = NULL;
			}
		}
		if(*port_mapping == NULL) {
			*port_mapping = linphone_upnp_port_binding_new_or_collect(lupnp->pending_bindings, protocol, port, port);
		}
		
		// Get addresses
		local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
		external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
810

811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834
		// Force binding update on local address change
		if(local_addr != NULL) {
			if(strncmp((*port_mapping)->local_addr, local_addr, sizeof((*port_mapping)->local_addr))) {
				linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
				strncpy((*port_mapping)->local_addr, local_addr, sizeof((*port_mapping)->local_addr));
			}
		}
		if(external_addr != NULL) {
			strncpy((*port_mapping)->external_addr, external_addr, sizeof((*port_mapping)->external_addr));
		}

		// Add (if not already done) the binding
		if(now - (*port_mapping)->last_update >= retry_delay) {
			(*port_mapping)->last_update = now;
			linphone_upnp_context_send_add_port_binding(lupnp, *port_mapping, FALSE);
		}
	} else {
		if(*port_mapping != NULL) {
			linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
			*port_mapping = NULL;
		}
	}
}

835
void linphone_upnp_update_config(UpnpContext* lupnp) {
836
	char key[64];
837
	const MSList *item;
838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866
	UpnpPortBinding *port_mapping;
	
	/* Add configs */
	for(item = lupnp->adding_configs;item!=NULL;item=item->next) {
		port_mapping = (UpnpPortBinding *)item->data;
		snprintf(key, sizeof(key), "%s-%d-%d",
					(port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
							port_mapping->external_port,
							port_mapping->local_port);
		lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, "uPnP");
		linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping);
	}
	ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
	lupnp->adding_configs = ms_list_free(lupnp->adding_configs);

	/* Remove configs */
	for(item = lupnp->removing_configs;item!=NULL;item=item->next) {
		port_mapping = (UpnpPortBinding *)item->data;
		snprintf(key, sizeof(key), "%s-%d-%d",
					(port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
							port_mapping->external_port,
							port_mapping->local_port);
		lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, NULL);
		linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping);
	}
	ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
	lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
}

Yann Diorcet's avatar
Yann Diorcet committed
867
void linphone_upnp_update_proxy(UpnpContext* lupnp, bool_t force) {
868
	LinphoneUpnpState ready_state;
869
	const MSList *item;
Yann Diorcet's avatar
Yann Diorcet committed
870 871
	time_t now = (force)? (lupnp->last_ready_check + UPNP_CORE_READY_CHECK) : time(NULL);
	
872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892
	/* Refresh registers if we are ready */
	if(now - lupnp->last_ready_check >= UPNP_CORE_READY_CHECK) {
		lupnp->last_ready_check = now;
		ready_state = (_linphone_upnp_context_is_ready_for_register(lupnp))? LinphoneUpnpStateOk: LinphoneUpnpStateKo;
		if(ready_state != lupnp->last_ready_state) {
			for(item=linphone_core_get_proxy_config_list(lupnp->lc);item!=NULL;item=item->next) {
				LinphoneProxyConfig *cfg=(LinphoneProxyConfig*)item->data;
				if (linphone_proxy_config_register_enabled(cfg)) {
					if (ready_state != LinphoneUpnpStateOk) {
						// Only reset ithe registration if we require that upnp should be ok
						if(lupnp->lc->sip_conf.register_only_when_upnp_is_ok) {
							linphone_proxy_config_set_state(cfg, LinphoneRegistrationNone, "Registration impossible (uPnP not ready)");
						}
					} else {
						cfg->commit=TRUE;
					}
				}
			}
			lupnp->last_ready_state = ready_state;
		}
	}
Yann Diorcet's avatar
Yann Diorcet committed
893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909
}

bool_t linphone_core_upnp_hook(void *data) {
	LCSipTransports transport;
	UpnpContext *lupnp = (UpnpContext *)data;

	ms_mutex_lock(&lupnp->mutex);

	/* Update ports */
	if(lupnp->state == LinphoneUpnpStateOk) {
		linphone_core_get_sip_transports(lupnp->lc, &transport);
		linphone_upnp_update_port_binding(lupnp, &lupnp->sip_udp, UPNP_IGD_IP_PROTOCOL_UDP, transport.udp_port, UPNP_CORE_RETRY_DELAY);
		linphone_upnp_update_port_binding(lupnp, &lupnp->sip_tcp, UPNP_IGD_IP_PROTOCOL_TCP, transport.tcp_port, UPNP_CORE_RETRY_DELAY);
		linphone_upnp_update_port_binding(lupnp, &lupnp->sip_tls, UPNP_IGD_IP_PROTOCOL_TCP, transport.tls_port, UPNP_CORE_RETRY_DELAY);
	}

	linphone_upnp_update_proxy(lupnp, FALSE);	
910
	linphone_upnp_update_config(lupnp);
Yann Diorcet's avatar
Yann Diorcet committed
911

Yann Diorcet's avatar
Yann Diorcet committed
912 913
	ms_mutex_unlock(&lupnp->mutex);
	return TRUE;
Yann Diorcet's avatar
Yann Diorcet committed
914 915
}

Yann Diorcet's avatar
Yann Diorcet committed
916
int linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
Yann Diorcet's avatar
Yann Diorcet committed
917 918 919 920
	int i;
	SalStreamDescription *stream;
	UpnpStream *upnpStream;

Yann Diorcet's avatar
Yann Diorcet committed
921
	for (i = 0; i < desc->n_active_streams; i++) {
Yann Diorcet's avatar
Yann Diorcet committed
922 923 924 925 926 927 928 929
		stream = &desc->streams[i];
		upnpStream = NULL;
		if(stream->type == SalAudio) {
			upnpStream = session->audio;
		} else if(stream->type == SalVideo) {
			upnpStream = session->video;
		}
		if(upnpStream != NULL) {
930
			if(upnpStream->rtp != NULL && upnpStream->rtp->state == LinphoneUpnpStateOk) {
Yann Diorcet's avatar
Yann Diorcet committed
931 932 933
				strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
				stream->rtp_port = upnpStream->rtp->external_port;
			}
934
			if(upnpStream->rtcp != NULL && upnpStream->rtcp->state == LinphoneUpnpStateOk) {
Yann Diorcet's avatar
Yann Diorcet committed
935 936 937 938
				strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
				stream->rtcp_port = upnpStream->rtcp->external_port;
			}
		}
Yann Diorcet's avatar
Yann Diorcet committed
939
	}
Yann Diorcet's avatar
Yann Diorcet committed
940
	return 0;
Yann Diorcet's avatar
Yann Diorcet committed
941 942
}

Yann Diorcet's avatar
Yann Diorcet committed
943 944 945 946 947

/*
 * uPnP Port Binding
 */

948
UpnpPortBinding *linphone_upnp_port_binding_new() {
Yann Diorcet's avatar
Yann Diorcet committed
949 950 951
	UpnpPortBinding *port = NULL;
	port = ms_new0(UpnpPortBinding,1);
	ms_mutex_init(&port->mutex, NULL);
Yann Diorcet's avatar
Yann Diorcet committed
952
	port->state = LinphoneUpnpStateIdle;
953
	port->protocol = UPNP_IGD_IP_PROTOCOL_UDP;	
Yann Diorcet's avatar
Yann Diorcet committed
954
	port->local_addr[0] = '\0';
Yann Diorcet's avatar
Yann Diorcet committed
955
	port->local_port = -1;
Yann Diorcet's avatar
Yann Diorcet committed
956 957
	port->external_addr[0] = '\0';
	port->external_port = -1;
958 959
	port->to_remove = FALSE;
	port->to_add = FALS