upnp.c 43 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"
23 24 25 26
#include <ctype.h>

#define UPNP_STRINGIFY(x)     #x
#define UPNP_TOSTRING(x)      UPNP_STRINGIFY(x)
Yann Diorcet's avatar
Yann Diorcet committed
27

28
#define UPNP_ADD_MAX_RETRY    4
Yann Diorcet's avatar
Yann Diorcet committed
29
#define UPNP_REMOVE_MAX_RETRY 4
30
#define UPNP_SECTION_NAME     "uPnP"
31
#define UPNP_CORE_READY_CHECK 1
Yann Diorcet's avatar
Yann Diorcet committed
32 33
#define UPNP_CORE_RETRY_DELAY 10
#define UPNP_CALL_RETRY_DELAY 3
Yann Diorcet's avatar
Yann Diorcet committed
34
#define UPNP_UUID_LEN         128
35
#define UPNP_UUID_LEN_STR     UPNP_TOSTRING(UPNP_UUID_LEN)
Yann Diorcet's avatar
Yann Diorcet committed
36 37 38 39 40 41
/*
 * uPnP Definitions
 */

typedef struct _UpnpPortBinding {
	ms_mutex_t mutex;
42
	LinphoneUpnpState state;
Yann Diorcet's avatar
Yann Diorcet committed
43
	upnp_igd_ip_protocol protocol;
44
	char *device_id;
Yann Diorcet's avatar
Yann Diorcet committed
45 46 47 48 49 50
	char local_addr[LINPHONE_IPADDR_SIZE];
	int local_port;
	char external_addr[LINPHONE_IPADDR_SIZE];
	int external_port;
	int retry;
	int ref;
51 52
	bool_t to_remove;
	bool_t to_add;
53
	time_t last_update;
Yann Diorcet's avatar
Yann Diorcet committed
54 55 56 57 58
} UpnpPortBinding;

typedef struct _UpnpStream {
	UpnpPortBinding *rtp;
	UpnpPortBinding *rtcp;
59
	LinphoneUpnpState state;
Yann Diorcet's avatar
Yann Diorcet committed
60 61 62 63 64 65
} UpnpStream;

struct _UpnpSession {
	LinphoneCall *call;
	UpnpStream *audio;
	UpnpStream *video;
66
	LinphoneUpnpState state;
Yann Diorcet's avatar
Yann Diorcet committed
67 68 69 70 71 72 73 74
};

struct _UpnpContext {
	LinphoneCore *lc;
	upnp_igd_context *upnp_igd_ctxt;
	UpnpPortBinding *sip_tcp;
	UpnpPortBinding *sip_tls;
	UpnpPortBinding *sip_udp;
75
	LinphoneUpnpState state;
Yann Diorcet's avatar
Yann Diorcet committed
76 77 78 79 80
	MSList *removing_configs;
	MSList *adding_configs;
	MSList *pending_bindings;

	ms_mutex_t mutex;
81
	ms_cond_t empty_cond;
82

83 84
	time_t last_ready_check;
	LinphoneUpnpState last_ready_state;
Yann Diorcet's avatar
Yann Diorcet committed
85 86 87
};


Yann Diorcet's avatar
Yann Diorcet committed
88
bool_t linphone_core_upnp_hook(void *data);
Yann Diorcet's avatar
Yann Diorcet committed
89 90
void linphone_upnp_update(UpnpContext *ctx);
bool_t linphone_upnp_is_blacklisted(UpnpContext *ctx);
Yann Diorcet's avatar
Yann Diorcet committed
91

92
UpnpPortBinding *linphone_upnp_port_binding_new();
93
UpnpPortBinding *linphone_upnp_port_binding_new_with_parameters(upnp_igd_ip_protocol protocol, int local_port, int external_port);
94
UpnpPortBinding *linphone_upnp_port_binding_new_or_collect(MSList *list, upnp_igd_ip_protocol protocol, int local_port, int external_port);
95
UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port);
96
void linphone_upnp_port_binding_set_device_id(UpnpPortBinding *port, const char * device_id);
97 98 99
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);
100
void linphone_upnp_update_port_binding(UpnpContext *lupnp, UpnpPortBinding **port_mapping, upnp_igd_ip_protocol protocol, int port, int retry_delay);
101 102
void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port);
void linphone_upnp_port_binding_release(UpnpPortBinding *port);
103
void linphone_upnp_update_config(UpnpContext *lupnp);
Yann Diorcet's avatar
Yann Diorcet committed
104
void linphone_upnp_update_proxy(UpnpContext *lupnp, bool_t force);
Yann Diorcet's avatar
Yann Diorcet committed
105

106
// Configuration
107
MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc, const char *device_id);
108 109
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
110

111
// uPnP
112 113
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
114

115 116 117
static int linphone_upnp_strncmpi(const char *str1, const char *str2, int len) {
	int i = 0;
	char char1, char2;
Yann Diorcet's avatar
Yann Diorcet committed
118
	while(i < len) {
119 120
		char1 = toupper(*str1);
		char2 = toupper(*str2);
Yann Diorcet's avatar
Yann Diorcet committed
121
		if(char1 == '\0' || char1 != char2) {
122 123 124 125
			return char1 - char2;
		}
		str1++;
		str2++;
126
		i++;
127
	}
Yann Diorcet's avatar
Yann Diorcet committed
128
	return 0;
129
}
130

131 132 133 134 135 136 137 138
static int linphone_upnp_str_min(const char *str1, const char *str2) {
	int len1 = strlen(str1);
	int len2 = strlen(str2);
	if(len1 > len2) {
		return len2;
	}
	return len1;
}
139

140 141 142 143
char * linphone_upnp_format_device_id(const char *device_id) {
	char *ret = NULL;
	char *tmp;
	char tchar;
144
	bool_t copy;
145 146 147
	if(device_id == NULL) {
		return ret;
	}
Simon Morlat's avatar
Simon Morlat committed
148
	ret = ms_new0(char, UPNP_UUID_LEN + 1);
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
	tmp = ret;
	if(linphone_upnp_strncmpi(device_id, "uuid:", linphone_upnp_str_min(device_id, "uuid:")) == 0) {
		device_id += strlen("uuid:");
	}
	while(*device_id != '\0' && tmp - ret < UPNP_UUID_LEN) {
		copy = FALSE;
		tchar = *device_id;
		if(tchar >= '0' && tchar <= '9')
			copy = TRUE;
		if(!copy && tchar >= 'A' && tchar <= 'Z')
			copy = TRUE;
		if(!copy && tchar >= 'a' && tchar <= 'z')
			copy = TRUE;
		if(copy) {
			*tmp = *device_id;
			tmp++;
		}
		device_id++;
	}
	*tmp = '\0';
	return ret;
}

Yann Diorcet's avatar
Yann Diorcet committed
172 173 174 175
/**
 * uPnP Callbacks
 */

Yann Diorcet's avatar
Yann Diorcet committed
176 177 178 179 180 181 182 183
/* 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
184
		ortp_level = ORTP_DEBUG; // Too verbose otherwise
Yann Diorcet's avatar
Yann Diorcet committed
185 186
		break;
	case UPNP_IGD_ERROR:
Yann Diorcet's avatar
Yann Diorcet committed
187
		ortp_level = ORTP_DEBUG; // Too verbose otherwise
Yann Diorcet's avatar
Yann Diorcet committed
188 189 190 191
		break;
	default:
		break;
	}
Yann Diorcet's avatar
Yann Diorcet committed
192
	ortp_logv(ortp_level, fmt, list);
Yann Diorcet's avatar
Yann Diorcet committed
193 194 195
}

void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) {
Yann Diorcet's avatar
Yann Diorcet committed
196
	UpnpContext *lupnp = (UpnpContext *)cookie;
Yann Diorcet's avatar
Yann Diorcet committed
197 198 199 200 201
	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;
Yann Diorcet's avatar
Yann Diorcet committed
202
	bool_t blacklisted = FALSE;
203 204 205 206 207 208 209
	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
210
	ms_mutex_lock(&lupnp->mutex);
211
	old_state = lupnp->state;
Yann Diorcet's avatar
Yann Diorcet committed
212

Yann Diorcet's avatar
Yann Diorcet committed
213
	switch(event) {
214 215
	case UPNP_IGD_DEVICE_ADDED:
	case UPNP_IGD_DEVICE_REMOVED:
Yann Diorcet's avatar
Yann Diorcet committed
216 217 218
	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
219 220 221
		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);
Yann Diorcet's avatar
Yann Diorcet committed
222
		blacklisted = linphone_upnp_is_blacklisted(lupnp);
Yann Diorcet's avatar
Yann Diorcet committed
223 224

		if(ip_address == NULL || connection_status == NULL) {
Yann Diorcet's avatar
Yann Diorcet committed
225 226
			ms_message("uPnP IGD: Pending");
			lupnp->state = LinphoneUpnpStatePending;
Yann Diorcet's avatar
Yann Diorcet committed
227
		} else if(strcasecmp(connection_status, "Connected")  || !nat_enabled) {
Yann Diorcet's avatar
Yann Diorcet committed
228 229
			ms_message("uPnP IGD: Not Available");
			lupnp->state = LinphoneUpnpStateNotAvailable;
Yann Diorcet's avatar
Yann Diorcet committed
230 231
		} else if(blacklisted) {
			ms_message("uPnP IGD: Router is blacklisted");
232
			lupnp->state = LinphoneUpnpStateBlacklisted;
Yann Diorcet's avatar
Yann Diorcet committed
233
		} else {
Yann Diorcet's avatar
Yann Diorcet committed
234 235
			ms_message("uPnP IGD: Connected");
			lupnp->state = LinphoneUpnpStateOk;
236
			if(old_state != LinphoneUpnpStateOk) {
Yann Diorcet's avatar
Yann Diorcet committed
237
				linphone_upnp_update(lupnp);
238
			}
Yann Diorcet's avatar
Yann Diorcet committed
239 240 241 242 243 244 245
		}

		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
246 247
		port_mapping->external_port = mapping->remote_port;
		port_mapping->state = LinphoneUpnpStateOk;
248 249
		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
250

Yann Diorcet's avatar
Yann Diorcet committed
251 252 253 254 255
		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
256
		port_mapping->external_port = -1; //Force random external port
257
		if(linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, TRUE) != 0) {
258
			linphone_upnp_port_binding_log(ORTP_ERROR, "Can't add port binding", port_mapping);
Yann Diorcet's avatar
Yann Diorcet committed
259 260
		}

Yann Diorcet's avatar
Yann Diorcet committed
261 262 263 264 265
		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
266
		port_mapping->state = LinphoneUpnpStateIdle;
267 268
		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
269

Yann Diorcet's avatar
Yann Diorcet committed
270 271 272 273 274
		break;

	case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE:
		mapping = (upnp_igd_port_mapping *) arg;
		port_mapping = (UpnpPortBinding*) mapping->cookie;
275
		if(linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE) != 0) {
276 277
			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
278 279
		}

Yann Diorcet's avatar
Yann Diorcet committed
280 281 282 283 284
		break;

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

286 287 288 289 290 291 292
	if(port_mapping != NULL) {
		/*
		 * Execute delayed actions
		 */
		if(port_mapping->to_remove) {
			if(port_mapping->state == LinphoneUpnpStateOk) {
				port_mapping->to_remove = FALSE;
293
				linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, FALSE);
294 295 296
			} else if(port_mapping->state == LinphoneUpnpStateKo) {
				port_mapping->to_remove = FALSE;
			}
Yann Diorcet's avatar
Yann Diorcet committed
297
		}
298 299 300
		if(port_mapping->to_add) {
			if(port_mapping->state == LinphoneUpnpStateIdle || port_mapping->state == LinphoneUpnpStateKo) {
				port_mapping->to_add = FALSE;
301
				linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, FALSE);
302 303 304 305 306 307 308 309 310 311 312
			}
		}

		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) {
313
		ms_cond_signal(&lupnp->empty_cond);
Yann Diorcet's avatar
Yann Diorcet committed
314
	}
Yann Diorcet's avatar
Yann Diorcet committed
315 316 317
	ms_mutex_unlock(&lupnp->mutex);
}

Yann Diorcet's avatar
Yann Diorcet committed
318 319 320 321 322

/**
 * uPnP Context
 */

323
UpnpContext* linphone_upnp_context_new(LinphoneCore *lc) {
Yann Diorcet's avatar
Yann Diorcet committed
324
	UpnpContext *lupnp = (UpnpContext *)ms_new0(UpnpContext,1);
jehan's avatar
jehan committed
325 326 327 328 329 330
	char address[LINPHONE_IPADDR_SIZE];
	const char*upnp_binding_address=address;
	if (linphone_core_get_local_ip_for(lc->sip_conf.ipv6_enabled ? AF_INET6 : AF_INET,NULL,address)) {
		ms_warning("Linphone core [%p] cannot guess local address for upnp, let's choice the lib",lc);
		upnp_binding_address=NULL;
	}
Yann Diorcet's avatar
Yann Diorcet committed
331
	ms_mutex_init(&lupnp->mutex, NULL);
332
	ms_cond_init(&lupnp->empty_cond, NULL);
Yann Diorcet's avatar
Yann Diorcet committed
333

334 335 336
	lupnp->last_ready_check = 0;
	lupnp->last_ready_state = LinphoneUpnpStateIdle;

Yann Diorcet's avatar
Yann Diorcet committed
337
	lupnp->lc = lc;
Yann Diorcet's avatar
Yann Diorcet committed
338 339 340
	lupnp->pending_bindings = NULL;
	lupnp->adding_configs = NULL;
	lupnp->removing_configs = NULL;
Yann Diorcet's avatar
Yann Diorcet committed
341
	lupnp->state = LinphoneUpnpStateIdle;
jehan's avatar
jehan committed
342
	ms_message("uPnP IGD: New %p for core %p bound to %s", lupnp, lc,upnp_binding_address);
Yann Diorcet's avatar
Yann Diorcet committed
343

344 345 346 347
	// Init ports
	lupnp->sip_udp = NULL;
	lupnp->sip_tcp = NULL;
	lupnp->sip_tls = NULL;
Yann Diorcet's avatar
Yann Diorcet committed
348

Yann Diorcet's avatar
Yann Diorcet committed
349
	linphone_core_add_iterate_hook(lc, linphone_core_upnp_hook, lupnp);
Yann Diorcet's avatar
Yann Diorcet committed
350 351

	lupnp->upnp_igd_ctxt = NULL;
jehan's avatar
jehan committed
352
	lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, address, lupnp);
Yann Diorcet's avatar
Yann Diorcet committed
353 354 355
	if(lupnp->upnp_igd_ctxt == NULL) {
		lupnp->state = LinphoneUpnpStateKo;
		ms_error("Can't create uPnP IGD context");
Yann Diorcet's avatar
Yann Diorcet committed
356
		return NULL;
Yann Diorcet's avatar
Yann Diorcet committed
357 358 359
	}

	lupnp->state = LinphoneUpnpStatePending;
360 361
	upnp_igd_start(lupnp->upnp_igd_ctxt);

Yann Diorcet's avatar
Yann Diorcet committed
362
	return lupnp;
Yann Diorcet's avatar
Yann Diorcet committed
363 364
}

365
void linphone_upnp_context_destroy(UpnpContext *lupnp) {
366
	linphone_core_remove_iterate_hook(lupnp->lc, linphone_core_upnp_hook, lupnp);
Yann Diorcet's avatar
Yann Diorcet committed
367

368
	ms_mutex_lock(&lupnp->mutex);
369

370 371 372 373 374 375 376 377 378 379 380
	if(lupnp->lc->network_reachable) {
		/* Send port binding removes */
		if(lupnp->sip_udp != NULL) {
			linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_udp, TRUE);
		}
		if(lupnp->sip_tcp != NULL) {
			linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tcp, TRUE);
		}
		if(lupnp->sip_tls != NULL) {
			linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tls, TRUE);
		}
Yann Diorcet's avatar
Yann Diorcet committed
381
	}
Yann Diorcet's avatar
Yann Diorcet committed
382 383

	/* Wait all pending bindings are done */
384 385 386 387
	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
388
	ms_mutex_unlock(&lupnp->mutex);
Yann Diorcet's avatar
Yann Diorcet committed
389

Yann Diorcet's avatar
Yann Diorcet committed
390 391
	if(lupnp->upnp_igd_ctxt != NULL) {
		upnp_igd_destroy(lupnp->upnp_igd_ctxt);
392
		lupnp->upnp_igd_ctxt = NULL;
Yann Diorcet's avatar
Yann Diorcet committed
393
	}
394

Yann Diorcet's avatar
Yann Diorcet committed
395
	/* No more multi threading here */
Yann Diorcet's avatar
Yann Diorcet committed
396

Yann Diorcet's avatar
Yann Diorcet committed
397
	/* Run one more time configuration update and proxy */
398
	linphone_upnp_update_config(lupnp);
Yann Diorcet's avatar
Yann Diorcet committed
399
	linphone_upnp_update_proxy(lupnp, TRUE);
Yann Diorcet's avatar
Yann Diorcet committed
400 401 402

	/* Release port bindings */
	if(lupnp->sip_udp != NULL) {
403
		linphone_upnp_port_binding_release(lupnp->sip_udp);
Yann Diorcet's avatar
Yann Diorcet committed
404 405 406
		lupnp->sip_udp = NULL;
	}
	if(lupnp->sip_tcp != NULL) {
407
		linphone_upnp_port_binding_release(lupnp->sip_tcp);
Yann Diorcet's avatar
Yann Diorcet committed
408 409 410
		lupnp->sip_tcp = NULL;
	}
	if(lupnp->sip_tls != NULL) {
411
		linphone_upnp_port_binding_release(lupnp->sip_tls);
Yann Diorcet's avatar
Yann Diorcet committed
412 413 414 415
		lupnp->sip_tcp = NULL;
	}

	/* Release lists */
416
	ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
Yann Diorcet's avatar
Yann Diorcet committed
417
	lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
418
	ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
Yann Diorcet's avatar
Yann Diorcet committed
419
	lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
420
	ms_list_for_each(lupnp->pending_bindings,(void (*)(void*))linphone_upnp_port_binding_release);
Yann Diorcet's avatar
Yann Diorcet committed
421
	lupnp->pending_bindings = ms_list_free(lupnp->pending_bindings);
422

Yann Diorcet's avatar
Yann Diorcet committed
423
	ms_mutex_destroy(&lupnp->mutex);
424
	ms_cond_destroy(&lupnp->empty_cond);
Yann Diorcet's avatar
Yann Diorcet committed
425

Yann Diorcet's avatar
Yann Diorcet committed
426 427
	ms_message("uPnP IGD: destroy %p", lupnp);
	ms_free(lupnp);
Yann Diorcet's avatar
Yann Diorcet committed
428 429
}

430
LinphoneUpnpState linphone_upnp_context_get_state(UpnpContext *lupnp) {
Yann Diorcet's avatar
Yann Diorcet committed
431 432 433 434 435 436
	LinphoneUpnpState state = LinphoneUpnpStateKo;
	if(lupnp != NULL) {
		ms_mutex_lock(&lupnp->mutex);
		state = lupnp->state;
		ms_mutex_unlock(&lupnp->mutex);
	}
437 438 439
	return state;
}

440
bool_t _linphone_upnp_context_is_ready_for_register(UpnpContext *lupnp) {
441
	bool_t ready = TRUE;
442

443 444
	// 1 Check global uPnP state
	ready = (lupnp->state == LinphoneUpnpStateOk);
445

446
	// 2 Check external ip address
447 448 449 450
	if(ready) {
		if (upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt) == NULL) {
			ready = FALSE;
		}
451
	}
452

453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470
	// 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;
		}
	}
471

472 473
	return ready;
}
474

475
bool_t linphone_upnp_context_is_ready_for_register(UpnpContext *lupnp) {
Yann Diorcet's avatar
Yann Diorcet committed
476 477 478 479 480 481
	bool_t ready = FALSE;
	if(lupnp != NULL) {
		ms_mutex_lock(&lupnp->mutex);
		ready = _linphone_upnp_context_is_ready_for_register(lupnp);
		ms_mutex_unlock(&lupnp->mutex);
	}
482 483 484
	return ready;
}

485 486
int linphone_upnp_context_get_external_port(UpnpContext *lupnp) {
	int port = -1;
Yann Diorcet's avatar
Yann Diorcet committed
487 488
	if(lupnp != NULL) {
		ms_mutex_lock(&lupnp->mutex);
489

Yann Diorcet's avatar
Yann Diorcet committed
490 491 492 493 494 495 496 497 498 499 500 501
		if(lupnp->sip_udp != NULL) {
			if(lupnp->sip_udp->state == LinphoneUpnpStateOk) {
				port = lupnp->sip_udp->external_port;
			}
		} else if(lupnp->sip_tcp != NULL) {
			if(lupnp->sip_tcp->state == LinphoneUpnpStateOk) {
				port = lupnp->sip_tcp->external_port;
			}
		} else if(lupnp->sip_tls != NULL) {
			if(lupnp->sip_tls->state == LinphoneUpnpStateOk) {
				port = lupnp->sip_tls->external_port;
			}
502
		}
503

Yann Diorcet's avatar
Yann Diorcet committed
504
		ms_mutex_unlock(&lupnp->mutex);
505 506
	}
	return port;
Yann Diorcet's avatar
Yann Diorcet committed
507 508
}

Yann Diorcet's avatar
Yann Diorcet committed
509 510
bool_t linphone_upnp_is_blacklisted(UpnpContext *lupnp) {
	const char * device_model_name = upnp_igd_get_device_model_name(lupnp->upnp_igd_ctxt);
511
	const char * device_model_number = upnp_igd_get_device_model_number(lupnp->upnp_igd_ctxt);
Yann Diorcet's avatar
Yann Diorcet committed
512 513 514 515 516 517 518 519 520 521 522 523
	const char * blacklist = lp_config_get_string(lupnp->lc->config, "net", "upnp_blacklist", NULL);
	bool_t blacklisted = FALSE;
	char *str;
	char *pch;
	char *model_name;
	char *model_number;

	// Sanity checks
	if(device_model_name == NULL || device_model_number == NULL || blacklist == NULL) {
		return FALSE;
	}

524
	// Find in the list
Yann Diorcet's avatar
Yann Diorcet committed
525 526 527 528 529 530 531 532 533 534 535 536 537 538 539
	str = strdup(blacklist);
	pch = strtok(str, ";");
	while (pch != NULL && !blacklisted) {
		// Extract model name & number
		model_name = pch;
		model_number = strstr(pch, ",");
		if(model_number != NULL) {
			*(model_number++) = '\0';
		}

		// Compare with current device
		if(strcmp(model_name, device_model_name) == 0) {
			if(model_number == NULL || strcmp(model_number, device_model_number) == 0) {
				blacklisted = TRUE;
			}
540
		}
Yann Diorcet's avatar
Yann Diorcet committed
541 542 543 544 545 546 547
		pch = strtok(NULL, ";");
	}
	free(str);

	return blacklisted;
}

548 549 550 551
void linphone_upnp_refresh(UpnpContext * lupnp) {
	upnp_igd_refresh(lupnp->upnp_igd_ctxt);
}

552 553
const char* linphone_upnp_context_get_external_ipaddress(UpnpContext *lupnp) {
	const char* addr = NULL;
Yann Diorcet's avatar
Yann Diorcet committed
554 555 556 557 558
	if(lupnp != NULL) {
		ms_mutex_lock(&lupnp->mutex);
		addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
		ms_mutex_unlock(&lupnp->mutex);
	}
559
	return addr;
Yann Diorcet's avatar
Yann Diorcet committed
560 561
}

562
int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry) {
Yann Diorcet's avatar
Yann Diorcet committed
563
	upnp_igd_port_mapping mapping;
564
	char description[128];
Yann Diorcet's avatar
Yann Diorcet committed
565
	int ret;
566

567 568 569
	if(lupnp->state != LinphoneUpnpStateOk) {
		return -2;
	}
570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588

	// 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
589
	}
590

591 592 593 594
	// No retry if specified
	if(port->retry != 0 && !retry) {
		return -1;
	}
Yann Diorcet's avatar
Yann Diorcet committed
595

Yann Diorcet's avatar
Yann Diorcet committed
596
	if(port->retry >= UPNP_ADD_MAX_RETRY) {
Yann Diorcet's avatar
Yann Diorcet committed
597 598
		ret = -1;
	} else {
599
		linphone_upnp_port_binding_set_device_id(port, upnp_igd_get_device_id(lupnp->upnp_igd_ctxt));
600
		mapping.cookie = linphone_upnp_port_binding_retain(port);
Yann Diorcet's avatar
Yann Diorcet committed
601 602
		lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);

Yann Diorcet's avatar
Yann Diorcet committed
603
		mapping.local_port = port->local_port;
Yann Diorcet's avatar
Yann Diorcet committed
604 605
		mapping.local_host = port->local_addr;
		if(port->external_port == -1)
606 607
			port->external_port = rand()%(0xffff - 1024) + 1024;
		mapping.remote_port = port->external_port;
Yann Diorcet's avatar
Yann Diorcet committed
608
		mapping.remote_host = "";
609
		snprintf(description, 128, "%s %s at %s:%d",
610
				"Linphone",
611 612 613
				(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
614 615 616
		mapping.protocol = port->protocol;

		port->retry++;
617
		linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to add port binding", port);
Yann Diorcet's avatar
Yann Diorcet committed
618 619 620
		ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
	}
	if(ret != 0) {
Yann Diorcet's avatar
Yann Diorcet committed
621
		port->state = LinphoneUpnpStateKo;
Yann Diorcet's avatar
Yann Diorcet committed
622 623 624 625
	}
	return ret;
}

626
int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry) {
Yann Diorcet's avatar
Yann Diorcet committed
627 628
	upnp_igd_port_mapping mapping;
	int ret;
629

630 631 632
	if(lupnp->state != LinphoneUpnpStateOk) {
		return -2;
	}
633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650

	// 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
651
	}
652

653 654 655 656
	// No retry if specified
	if(port->retry != 0 && !retry) {
		return 1;
	}
Yann Diorcet's avatar
Yann Diorcet committed
657

Yann Diorcet's avatar
Yann Diorcet committed
658
	if(port->retry >= UPNP_REMOVE_MAX_RETRY) {
Yann Diorcet's avatar
Yann Diorcet committed
659 660
		ret = -1;
	} else {
661
		linphone_upnp_port_binding_set_device_id(port, upnp_igd_get_device_id(lupnp->upnp_igd_ctxt));
662
		mapping.cookie = linphone_upnp_port_binding_retain(port);
Yann Diorcet's avatar
Yann Diorcet committed
663 664
		lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);

Yann Diorcet's avatar
Yann Diorcet committed
665
		mapping.remote_port = port->external_port;
Yann Diorcet's avatar
Yann Diorcet committed
666 667 668
		mapping.remote_host = "";
		mapping.protocol = port->protocol;
		port->retry++;
669
		linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to remove port binding", port);
Yann Diorcet's avatar
Yann Diorcet committed
670 671 672
		ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
	}
	if(ret != 0) {
Yann Diorcet's avatar
Yann Diorcet committed
673
		port->state = LinphoneUpnpStateKo;
Yann Diorcet's avatar
Yann Diorcet committed
674 675 676 677
	}
	return ret;
}

Yann Diorcet's avatar
Yann Diorcet committed
678 679 680 681
/*
 * uPnP Core interfaces
 */

Yann Diorcet's avatar
Yann Diorcet committed
682
int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool_t video) {
Yann Diorcet's avatar
Yann Diorcet committed
683
	LinphoneCore *lc = call->core;
Yann Diorcet's avatar
Yann Diorcet committed
684
	UpnpContext *lupnp = lc->upnp;
Yann Diorcet's avatar
Yann Diorcet committed
685 686
	int ret = -1;

Yann Diorcet's avatar
Yann Diorcet committed
687 688 689 690
	if(lupnp == NULL) {
		return ret;
	}

Yann Diorcet's avatar
Yann Diorcet committed
691
	ms_mutex_lock(&lupnp->mutex);
692

Yann Diorcet's avatar
Yann Diorcet committed
693
	// Don't handle when the call
Yann Diorcet's avatar
Yann Diorcet committed
694
	if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
Yann Diorcet's avatar
Yann Diorcet committed
695 696 697 698 699
		ret = 0;

		/*
		 * Audio part
		 */
700
		linphone_upnp_update_port_binding(lupnp, &call->upnp_session->audio->rtp,
701
			UPNP_IGD_IP_PROTOCOL_UDP, (audio)? call->media_ports[call->main_audio_stream_index].rtp_port:0, UPNP_CALL_RETRY_DELAY);
Yann Diorcet's avatar
Yann Diorcet committed
702

703
		linphone_upnp_update_port_binding(lupnp, &call->upnp_session->audio->rtcp,
704
			UPNP_IGD_IP_PROTOCOL_UDP, (audio)? call->media_ports[call->main_audio_stream_index].rtcp_port:0, UPNP_CALL_RETRY_DELAY);
705

Yann Diorcet's avatar
Yann Diorcet committed
706 707 708
		/*
		 * Video part
		 */
709
		linphone_upnp_update_port_binding(lupnp, &call->upnp_session->video->rtp,
710
			UPNP_IGD_IP_PROTOCOL_UDP, (video)? call->media_ports[call->main_video_stream_index].rtp_port:0, UPNP_CALL_RETRY_DELAY);
711

712
		linphone_upnp_update_port_binding(lupnp, &call->upnp_session->video->rtcp,
713
			UPNP_IGD_IP_PROTOCOL_UDP, (video)? call->media_ports[call->main_video_stream_index].rtcp_port:0, UPNP_CALL_RETRY_DELAY);
Yann Diorcet's avatar
Yann Diorcet committed
714 715 716
	}

	ms_mutex_unlock(&lupnp->mutex);
Yann Diorcet's avatar
Yann Diorcet committed
717 718 719 720

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

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


727

Yann Diorcet's avatar
Yann Diorcet committed
728 729 730 731 732 733
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;

734
	for (i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) {
Yann Diorcet's avatar
Yann Diorcet committed
735
		stream = &md->streams[i];
736
		if (!sal_stream_description_active(stream)) continue;
Yann Diorcet's avatar
Yann Diorcet committed
737 738 739 740 741 742 743 744 745 746 747 748 749 750
		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
751 752 753 754 755
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;
}

756 757 758 759
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;
760 761 762 763
	} else if((stream->rtp != NULL &&
	   (stream->rtp->state == LinphoneUpnpStateAdding || stream->rtp->state == LinphoneUpnpStateRemoving)) ||
		  (stream->rtcp != NULL &&
			 (stream->rtcp->state == LinphoneUpnpStateAdding || stream->rtcp->state == LinphoneUpnpStateRemoving))) {
764 765 766 767 768
		stream->state = LinphoneUpnpStatePending;
	} else if((stream->rtp != NULL && stream->rtp->state == LinphoneUpnpStateKo) ||
			(stream->rtcp != NULL && stream->rtcp->state == LinphoneUpnpStateKo)) {
		stream->state = LinphoneUpnpStateKo;
	} else {
769
		ms_error("Invalid stream %p state", stream);
770 771 772
	}
}

773
int linphone_upnp_call_process(LinphoneCall *call) {
Yann Diorcet's avatar
Yann Diorcet committed
774
	LinphoneCore *lc = call->core;
Yann Diorcet's avatar
Yann Diorcet committed
775
	UpnpContext *lupnp = lc->upnp;
Yann Diorcet's avatar
Yann Diorcet committed
776
	int ret = -1;
777
	LinphoneUpnpState oldState = 0, newState = 0;
Yann Diorcet's avatar
Yann Diorcet committed
778

Yann Diorcet's avatar
Yann Diorcet committed
779 780 781 782
	if(lupnp == NULL) {
		return ret;
	}

Yann Diorcet's avatar
Yann Diorcet committed
783 784 785 786 787 788 789 790 791
	ms_mutex_lock(&lupnp->mutex);

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

		/*
		 * Update Audio state
		 */
792
		linphone_upnp_update_stream_state(call->upnp_session->audio);
Yann Diorcet's avatar
Yann Diorcet committed
793 794 795 796

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

Yann Diorcet's avatar
Yann Diorcet committed
799 800 801 802
		/*
		 * Update stat
		 */
		linphone_core_update_upnp_state_in_call_stats(call);
803

Yann Diorcet's avatar
Yann Diorcet committed
804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819
		/*
		 * 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
820
		newState = call->upnp_session->state;
Yann Diorcet's avatar
Yann Diorcet committed
821
	}
Yann Diorcet's avatar
Yann Diorcet committed
822

Yann Diorcet's avatar
Yann Diorcet committed
823
	ms_mutex_unlock(&lupnp->mutex);
824

Yann Diorcet's avatar
Yann Diorcet committed
825 826 827 828 829 830 831 832 833 834 835 836 837
	/* 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:
838
				linphone_core_start_accept_call_update(lc, call,call->prevstate,linphone_call_state_to_string(call->prevstate));
Yann Diorcet's avatar
Yann Diorcet committed
839 840 841 842 843
				break;
			case LinphoneCallOutgoingInit:
				linphone_core_proceed_with_invite_if_ready(lc, call, NULL);
				break;
			case LinphoneCallIdle:
844 845
				linphone_call_update_local_media_description_from_ice_or_upnp(call);
				sal_call_set_local_media_description(call->op,call->localdesc);
Yann Diorcet's avatar
Yann Diorcet committed
846 847 848 849 850
				linphone_core_notify_incoming_call(lc, call);
				break;
			default:
				break;
		}
Yann Diorcet's avatar
Yann Diorcet committed
851 852
	}

Yann Diorcet's avatar
Yann Diorcet committed
853 854 855
	return ret;
}

Yann Diorcet's avatar
Yann Diorcet committed
856 857 858 859 860 861 862 863
static const char *linphone_core_upnp_get_charptr_null(const char *str) {
	if(str != NULL) {
		return str;
	}
	return "(Null)";
}

void linphone_upnp_update(UpnpContext *lupnp) {
864
	MSList *global_list = NULL;
Yann Diorcet's avatar
Yann Diorcet committed
865 866 867
	MSList *list = NULL;
	MSList *item;
	LinphoneCall *call;
868
	UpnpPortBinding *port_mapping, *port_mapping2;
Yann Diorcet's avatar
Yann Diorcet committed
869

Yann Diorcet's avatar
Yann Diorcet committed
870
	ms_message("uPnP IGD: Name:%s", linphone_core_upnp_get_charptr_null(upnp_igd_get_device_name(lupnp->upnp_igd_ctxt)));
871 872 873
	ms_message("uPnP IGD: Device:%s %s",
				   linphone_core_upnp_get_charptr_null(upnp_igd_get_device_model_name(lupnp->upnp_igd_ctxt)),
			   linphone_core_upnp_get_charptr_null(upnp_igd_get_device_model_number(lupnp->upnp_igd_ctxt)));
874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889
	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) {
890 891 892 893 894 895 896 897 898 899 900 901
			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
902
		}
903 904 905
		list = list->next;
	}

906
	list = linphone_upnp_config_list_port_bindings(lupnp->lc->config, upnp_igd_get_device_id(lupnp->upnp_igd_ctxt));
907 908 909 910
	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) {
911
				linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE);
912 913 914 915
			} else if(port_mapping2->state == LinphoneUpnpStateIdle){
				/* Force to remove */
				port_mapping2->state = LinphoneUpnpStateOk;
			}
Yann Diorcet's avatar
Yann Diorcet committed
916
	}
917 918
	ms_list_for_each(list, (void (*)(void*))linphone_upnp_port_binding_release);
	list = ms_list_free(list);
Yann Diorcet's avatar
Yann Diorcet committed
919

Yann Diorcet's avatar
Yann Diorcet committed
920

921 922 923 924
	// (Re)Add removed port bindings
	list = global_list;
	while(list != NULL) {
		port_mapping = (UpnpPortBinding *)list->data;
925 926
		linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE);
		linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, TRUE);
927
		list = list->next;
Yann Diorcet's avatar
Yann Diorcet committed
928
	}
929 930 931
	global_list = ms_list_free(global_list);
}

932 933 934 935 936 937 938 939 940 941 942
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) {
Yann Diorcet's avatar
Yann Diorcet committed
943
			*port_mapping = linphone_upnp_port_binding_new_or_collect(lupnp->pending_bindings, protocol, port, port);
944
		}
945

946 947 948
		// Get addresses
		local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
		external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
949

950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973
		// 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;
		}
	}
}

974
void linphone_upnp_update_config(UpnpContext* lupnp) {
975
	char key[64];
976
	const MSList *item;
977
	UpnpPortBinding *port_mapping;
978

979 980 981
	/* Add configs */
	for(item = lupnp->adding_configs;item!=NULL;item=item->next) {
		port_mapping = (UpnpPortBinding *)item->data;
982 983
		snprintf(key, sizeof(key), "%s-%s-%d-%d",
					port_mapping->device_id,
984
					(port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
985 986
					port_mapping->external_port,
					port_mapping->local_port);
987 988 989 990 991 992 993 994 995
		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;
996 997
		snprintf(key, sizeof(key), "%s-%s-%d-%d",
					port_mapping->device_id,
998
					(port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
999 1000
					port_mapping->external_port,
					port_mapping->local_port);
1001 1002 1003 1004 1005 1006 1007
		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
1008
void linphone_upnp_update_proxy(UpnpContext* lupnp, bool_t force) {
1009
	LinphoneUpnpState ready_state;
1010
	const MSList *item;
Yann Diorcet's avatar
Yann Diorcet committed
1011
	time_t now = (force)? (lupnp->last_ready_check + UPNP_CORE_READY_CHECK) : time(NULL);
1012

1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024
	/* 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)");
1025 1026
						} else {
							cfg->commit=TRUE;
1027 1028 1029 1030 1031 1032 1033 1034 1035
						}
					} else {
						cfg->commit=TRUE;
					}
				}
			}
			lupnp->last_ready_state = ready_state;
		}
	}
Yann Diorcet's avatar
Yann Diorcet committed
1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051
}

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);
	}

1052
	linphone_upnp_update_proxy(lupnp, FALSE);
1053
	linphone_upnp_update_config(lupnp);
Yann Diorcet's avatar
Yann Diorcet committed
1054

Yann Diorcet's avatar
Yann Diorcet committed
1055 1056
	ms_mutex_unlock(&lupnp->mutex);
	return TRUE;
Yann Diorcet's avatar
Yann Diorcet committed
1057 1058
}

Yann Diorcet's avatar
Yann Diorcet committed
1059
int linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
Yann Diorcet's avatar
Yann Diorcet committed
1060 1061 1062 1063
	int i;
	SalStreamDescription *stream;
	UpnpStream *upnpStream;

1064
	for (i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) {
Yann Diorcet's avatar
Yann Diorcet committed
1065
		stream = &desc->streams[i];
1066
		if (!sal_stream_description_active(stream)) continue;
Yann Diorcet's avatar
Yann Diorcet committed
1067 1068 1069 1070 1071 1072 1073
		upnpStream = NULL;
		if(stream->type == SalAudio) {
			upnpStream = session->audio;
		} else if(stream->type == SalVideo) {
			upnpStream = session->video;
		}
		if(upnpStream != NULL) {
1074
			if(upnpStream->rtp != NULL && upnpStream->rtp->state == LinphoneUpnpStateOk) {
Yann Diorcet's avatar
Yann Diorcet committed
1075 1076 1077
				strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
				stream->rtp_port = upnpStream->rtp->external_port;
			}
1078
			if(upnpStream->rtcp != NULL && upnpStream->rtcp->state == LinphoneUpnpStateOk) {
Yann Diorcet's avatar
Yann Diorcet committed
1079 1080 1081 1082
				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
1083
	}
Yann Diorcet's avatar
Yann Diorcet committed
1084
	return 0;
Yann Diorcet's avatar
Yann Diorcet committed
1085 1086
}

Yann Diorcet's avatar
Yann Diorcet committed
1087 1088 1089 1090 1091

/*
 * uPnP Port Binding
 */

1092
UpnpPortBinding *linphone_upnp_port_binding_new() {
Yann Diorcet's avatar
Yann Diorcet committed
1093 1094 1095
	UpnpPortBinding *port = NULL;
	port = ms_new0(UpnpPortBinding,1);
	ms_mutex_init(&port->mutex, NULL);
Yann Diorcet's avatar
Yann Diorcet committed
1096
	port->state = LinphoneUpnpStateIdle;
1097
	port->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
1098
	port->device_id = NULL;
Yann Diorcet's avatar
Yann Diorcet committed
1099
	port->local_addr[0] = '\0';
Yann Diorcet's avatar
Yann Diorcet committed
1100
	port->local_port = -1;
Yann Diorcet's avatar
Yann Diorcet committed
1101 1102
	port->external_addr[0] = '\0';
	port->external_port = -1;
1103 1104
	port->to_remove = FALSE;
	port->to_add = FALSE;
Yann Diorcet's avatar
Yann Diorcet committed
1105
	port->ref = 1;
1106
	port->last_update = 0;
Yann Diorcet's avatar
Yann Diorcet committed
1107 1108 1109
	return port;
}

1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120
UpnpPortBinding *linphone_upnp_port_binding_new_with_parameters(upnp_igd_ip_protocol protocol, int local_port, int external_port) {
	UpnpPortBinding *port_binding = linphone_upnp_port_binding_new();
	port_binding->protocol = protocol;
	port_binding->local_port = local_port;
	port_binding->external_port = external_port;
	return port_binding;
}

UpnpPortBinding *linphone_upnp_port_binding_new_or_collect(MSList *list, upnp_igd_ip_protocol protocol, int local_port, int external_port) {
	UpnpPortBinding *tmp_binding;
	UpnpPortBinding *end_binding;
1121

Yann Diorcet's avatar
Yann Diorcet committed
1122 1123
	// Seek an binding with same protocol and local port
	end_binding = linphone_upnp_port_binding_new_with_parameters(protocol, local_port, -1);
1124
	tmp_binding = linphone_upnp_port_binding_equivalent_in_list(list, end_binding);
1125 1126

	// Must be not attached to any struct
Yann Diorcet's avatar
Yann Diorcet committed
1127
	if(tmp_binding != NULL && tmp_binding->ref == 1) {
1128
		linphone_upnp_port_binding_release(end_binding);
Yann Diorcet's avatar
Yann Diorcet committed
1129 1130 1131
		end_binding = linphone_upnp_port_binding_retain(tmp_binding);
	} else {
		end_binding->external_port = external_port;
1132
	}
1133 1134
	return end_binding;
}
1135

1136
UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port) {
Yann Diorcet's avatar
Yann Diorcet committed
1137 1138 1139
	UpnpPortBinding *new_port = NULL;
	new_port = ms_new0(UpnpPortBinding,1);
	memcpy(new_port, port, sizeof(UpnpPortBinding));
1140 1141
	new_port->device_id = NULL;
	linphone_upnp_port_binding_set_device_id(new_port, port->device_id);
Yann Diorcet's avatar
Yann Diorcet committed
1142 1143 1144 1145 1146
	ms_mutex_init(&new_port->mutex, NULL);
	new_port->ref = 1;
	return new_port;
}

1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160
void linphone_upnp_port_binding_set_device_id(UpnpPortBinding *port, const char *device_id) {
	char *formated_device_id = linphone_upnp_format_device_id(device_id);
	if(formated_device_id != NULL && port->device_id != NULL) {
		if(strcmp(formated_device_id, port->device_id) == 0) {
			ms_free(formated_device_id);
			return;
		}
	}
	if(port->device_id != NULL) {
		ms_free(port->device_id);
	}
	port->device_id = formated_device_id;
}

1161
void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port) {
Yann Diorcet's avatar
Yann Diorcet committed
1162
	if(strlen(port->local_addr)) {
1163
		ortp_log(level, "uPnP IGD: %s %s|%d->%s:%d (retry %d)", msg,
Yann Diorcet's avatar
Yann Diorcet committed
1164 1165 1166
							(port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
									port->external_port,
									port->local_addr,
1167 1168
									port->local_port,
									port->retry - 1);
Yann Diorcet's avatar
Yann Diorcet committed
1169
	} else {
1170
		ortp_log(level, "uPnP IGD: %s %s|%d->%d (retry %d)", msg,
Yann Diorcet's avatar
Yann Diorcet committed
1171 1172
							(port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
									port->external_port,
1173 1174
									port->local_port,
									port->retry - 1);
Yann Diorcet's avatar
Yann Diorcet committed
1175 1176 1177
	}
}

Yann Diorcet's avatar
Yann Diorcet committed
1178
// Return true if the binding are equivalent. (Note external_port == -1 means "don't care")
1179
bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
1180
	return port1->protocol == port2->protocol &&
1181 1182
		   port1->local_port == port2->local_port &&
		   (port1->external_port == -1 || port2->external_port == -1 || port1->external_port == port2->external_port);
Yann Diorcet's avatar
Yann Diorcet committed
1183 1184
}

1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198
UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port) {
	UpnpPortBinding *port_mapping;
	while(list != NULL) {
		port_mapping = (UpnpPortBinding *)list->data;
		if(linphone_upnp_port_binding_equal(port, port_mapping)) {
			return port_mapping;
		}
		list = list->next;
	}

	return NULL;
}

UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port) {
Yann Diorcet's avatar
Yann Diorcet committed
1199 1200 1201 1202 1203 1204
	ms_mutex_lock(&port->mutex);
	port->ref++;
	ms_mutex_unlock(&port->mutex);
	return port;
}

1205
void linphone_upnp_port_binding_release(UpnpPortBinding *port) {
Yann Diorcet's avatar
Yann Diorcet committed
1206 1207
	ms_mutex_lock(&port->mutex);
	if(--port->ref == 0) {
1208 1209 1210
		if(port->device_id != NULL) {
			ms_free(port->device_id);
		}
Yann Diorcet's avatar
Yann Diorcet committed
1211 1212 1213 1214 1215 1216
		ms_mutex_unlock(&port->mutex);
		ms_mutex_destroy(&port->mutex);
		ms_free(port);
		return;
	}
	ms_mutex_unlock(&port->mutex);
Yann Diorcet's avatar
Yann Diorcet committed
1217 1218
}

Yann Diorcet's avatar
Yann Diorcet committed
1219

Yann Diorcet's avatar
Yann Diorcet committed
1220 1221 1222 1223
/*
 * uPnP Stream
 */

1224
UpnpStream* linphone_upnp_stream_new() {
Yann Diorcet's avatar
Yann Diorcet committed
1225 1226
	UpnpStream *stream = ms_new0(UpnpStream,1);
	stream->state = LinphoneUpnpStateIdle;
1227
	stream->rtp = NULL;
1228
	stream->rtcp = NULL;
Yann Diorcet's avatar
Yann Diorcet committed
1229
	return stream;
Yann Diorcet's avatar
Yann Diorcet committed
1230 1231
}

1232
void linphone_upnp_stream_destroy(UpnpStream* stream) {
1233 1234 1235 1236 1237 1238 1239 1240
	if(stream->rtp != NULL) {
		linphone_upnp_port_binding_release(stream->rtp);
		stream->rtp = NULL;
	}
	if(stream->rtcp != NULL) {
		linphone_upnp_port_binding_release(stream->rtcp);
		stream->rtcp = NULL;
	}
Yann Diorcet's avatar
Yann Diorcet committed
1241 1242 1243 1244 1245 1246 1247 1248
	ms_free(stream);
}


/*
 * uPnP Session
 */

1249
UpnpSession* linphone_upnp_session_new(LinphoneCall* call) {
Yann Diorcet's avatar
Yann Diorcet committed
1250
	UpnpSession *session = ms_new0(UpnpSession,1);
Yann Diorcet's avatar
Yann Diorcet committed
1251
	session->call = call;
Yann Diorcet's avatar
Yann Diorcet committed
1252
	session->state = LinphoneUpnpStateIdle;
1253 1254
	session->audio = linphone_upnp_stream_new();
	session->video = linphone_upnp_stream_new();
Yann Diorcet's avatar
Yann Diorcet committed
1255
	return session;
Yann Diorcet's avatar
Yann Diorcet committed
1256
}
Yann Diorcet's avatar
Yann Diorcet committed
1257

1258
void linphone_upnp_session_destroy(UpnpSession *session) {
Yann Diorcet's avatar
Yann Diorcet committed
1259
	LinphoneCore *lc = session->call->core;
Yann Diorcet's avatar
Yann Diorcet committed
1260

Yann Diorcet's avatar
Yann Diorcet committed
1261 1262
	if(lc->upnp != NULL) {
		/* Remove bindings */
1263 1264 1265 1266 1267 1268 1269 1270 1271 1272