3 Copyright (C) 2012 Belledonne Communications SARL
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #define UPNP_MAX_RETRY 4
24 #define UPNP_SECTION_NAME "uPnP"
26 /* Define private types */
27 typedef struct _LpItem{
32 typedef struct _LpSection{
37 typedef struct _LpConfig{
45 /* Declare private functions */
46 LpSection *lp_config_find_section(LpConfig *lpconfig, const char *name);
47 void lp_section_remove_item(LpSection *sec, LpItem *item);
48 void lp_config_set_string(LpConfig *lpconfig,const char *section, const char *key, const char *value);
50 bool_t linphone_core_upnp_hook(void *data);
52 UpnpPortBinding *upnp_port_binding_new();
53 UpnpPortBinding *upnp_port_binding_copy(const UpnpPortBinding *port);
54 bool_t upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2);
55 UpnpPortBinding *upnp_port_binding_retain(UpnpPortBinding *port);
56 void upnp_port_binding_release(UpnpPortBinding *port);
58 MSList *upnp_config_list_port_bindings(struct _LpConfig *lpc);
59 int upnp_config_add_port_binding(LinphoneCore *lc, const UpnpPortBinding *port);
60 int upnp_config_remove_port_binding(LinphoneCore *lc, const UpnpPortBinding *port);
62 int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *port);
63 int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port);
69 /* Convert uPnP IGD logs to ortp logs */
70 void linphone_upnp_igd_print(void *cookie, upnp_igd_print_level level, const char *fmt, va_list list) {
71 int ortp_level = ORTP_DEBUG;
73 case UPNP_IGD_MESSAGE:
74 ortp_level = ORTP_MESSAGE;
76 case UPNP_IGD_WARNING:
77 ortp_level = ORTP_WARNING;
80 ortp_level = ORTP_ERROR;
85 ortp_logv(ortp_level, fmt, list);
88 void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) {
89 LinphoneCore *lc = (LinphoneCore *)cookie;
90 UpnpContext *lupnp = &lc->upnp;
91 upnp_igd_port_mapping *mapping = NULL;
92 UpnpPortBinding *port_mapping = NULL;
93 const char *ip_address = NULL;
94 const char *connection_status = NULL;
95 bool_t nat_enabled = FALSE;
96 ms_mutex_lock(&lupnp->mutex);
99 case UPNP_IGD_EXTERNAL_IPADDRESS_CHANGED:
100 case UPNP_IGD_NAT_ENABLED_CHANGED:
101 case UPNP_IGD_CONNECTION_STATUS_CHANGED:
102 ip_address = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
103 connection_status = upnp_igd_get_connection_status(lupnp->upnp_igd_ctxt);
104 nat_enabled = upnp_igd_get_nat_enabled(lupnp->upnp_igd_ctxt);
106 if(ip_address == NULL || connection_status == NULL) {
107 ms_message("uPnP IGD: Pending");
108 lupnp->state = LinphoneUpnpStatePending;
109 } else if(strcasecmp(connection_status, "Connected") || !nat_enabled) {
110 ms_message("uPnP IGD: Not Available");
111 lupnp->state = LinphoneUpnpStateNotAvailable;
113 ms_message("uPnP IGD: Connected");
114 lupnp->state = LinphoneUpnpStateOk;
119 case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS:
120 mapping = (upnp_igd_port_mapping *) arg;
121 port_mapping = (UpnpPortBinding*) mapping->cookie;
122 port_mapping->external_port = mapping->remote_port;
123 port_mapping->state = LinphoneUpnpStateOk;
124 ms_message("uPnP IGD: Added port binding %s|%d->%s:%d",
125 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
126 port_mapping->external_port,
127 port_mapping->local_addr,
128 port_mapping->local_port);
129 upnp_config_add_port_binding(lc, port_mapping);
131 upnp_port_binding_release(port_mapping);
134 case UPNP_IGD_PORT_MAPPING_ADD_FAILURE:
135 mapping = (upnp_igd_port_mapping *) arg;
136 port_mapping = (UpnpPortBinding*) mapping->cookie;
137 if(upnp_context_send_add_port_binding(lc, port_mapping) != 0) {
138 ms_error("uPnP IGD: Can't add port binding %s|%d->%s:%d",
139 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
140 port_mapping->external_port,
141 port_mapping->local_addr,
142 port_mapping->local_port);
145 upnp_port_binding_release(port_mapping);
148 case UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS:
149 mapping = (upnp_igd_port_mapping *) arg;
150 port_mapping = (UpnpPortBinding*) mapping->cookie;
151 port_mapping->state = LinphoneUpnpStateIdle;
152 ms_message("uPnP IGD: Removed port binding %s|%d->%d",
153 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
154 port_mapping->external_port,
155 port_mapping->local_port);
156 upnp_config_remove_port_binding(lc, port_mapping);
158 upnp_port_binding_release(port_mapping);
161 case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE:
162 mapping = (upnp_igd_port_mapping *) arg;
163 port_mapping = (UpnpPortBinding*) mapping->cookie;
164 if(upnp_context_send_remove_port_binding(lc, port_mapping) != 0) {
165 ms_error("uPnP IGD: Can't remove port binding %s|%d->%d",
166 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
167 port_mapping->external_port,
168 port_mapping->local_port);
169 upnp_config_remove_port_binding(lc, port_mapping);
172 upnp_port_binding_release(port_mapping);
179 ms_mutex_unlock(&lupnp->mutex);
187 int upnp_context_init(LinphoneCore *lc) {
188 LCSipTransports transport;
189 UpnpContext *lupnp = &lc->upnp;
190 const char *ip_address;
192 ms_mutex_init(&lupnp->mutex, NULL);
193 lupnp->pending_configs = NULL;
194 lupnp->state = LinphoneUpnpStateIdle;
195 lupnp->old_state = LinphoneUpnpStateIdle;
196 ms_message("uPnP IGD: Init");
198 linphone_core_get_sip_transports(lc, &transport);
199 if(transport.udp_port != 0) {
200 lupnp->sip_udp = upnp_port_binding_new();
201 lupnp->sip_udp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
202 lupnp->sip_udp->local_port = transport.udp_port;
204 lupnp->sip_udp = NULL;
206 if(transport.tcp_port != 0) {
207 lupnp->sip_tcp = upnp_port_binding_new();
208 lupnp->sip_tcp->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
209 lupnp->sip_tcp->local_port = transport.tcp_port;
211 lupnp->sip_tcp = NULL;
213 if(transport.tls_port != 0) {
214 lupnp->sip_tls = upnp_port_binding_new();
215 lupnp->sip_tls->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
216 lupnp->sip_tls->local_port = transport.tls_port;
218 lupnp->sip_tls = NULL;
221 linphone_core_add_iterate_hook(lc, linphone_core_upnp_hook, lc);
223 lupnp->upnp_igd_ctxt = NULL;
224 lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, lc);
225 if(lupnp->upnp_igd_ctxt == NULL) {
226 lupnp->state = LinphoneUpnpStateKo;
227 ms_error("Can't create uPnP IGD context");
231 ip_address = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
232 if(lupnp->sip_udp != NULL) {
233 strncpy(lupnp->sip_udp->local_addr, ip_address, sizeof(lupnp->sip_udp->local_addr));
235 if(lupnp->sip_tcp != NULL) {
236 strncpy(lupnp->sip_tcp->local_addr, ip_address, sizeof(lupnp->sip_tcp->local_addr));
238 if(lupnp->sip_tls != NULL) {
239 strncpy(lupnp->sip_tls->local_addr, ip_address, sizeof(lupnp->sip_tls->local_addr));
242 lupnp->state = LinphoneUpnpStatePending;
246 void upnp_context_uninit(LinphoneCore *lc) {
247 UpnpContext *lupnp = &lc->upnp;
248 linphone_core_remove_iterate_hook(lc, linphone_core_upnp_hook, lc);
250 if(lupnp->sip_udp != NULL) {
251 upnp_port_binding_release(lupnp->sip_udp);
253 if(lupnp->sip_tcp != NULL) {
254 upnp_port_binding_release(lupnp->sip_tcp);
256 if(lupnp->sip_tls != NULL) {
257 upnp_port_binding_release(lupnp->sip_tls);
259 if(lupnp->upnp_igd_ctxt != NULL) {
260 upnp_igd_destroy(lupnp->upnp_igd_ctxt);
262 ms_mutex_destroy(&lupnp->mutex);
264 ms_message("uPnP IGD: Uninit");
267 int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port) {
268 UpnpContext *lupnp = &lc->upnp;
269 upnp_igd_port_mapping mapping;
271 if(port->state == LinphoneUpnpStateIdle) {
272 port->external_port = -1;
274 port->state = LinphoneUpnpStateAdding;
275 } else if(port->state != LinphoneUpnpStateAdding) {
276 ms_error("uPnP: try to add a port binding in wrong state: %d", port->state);
280 if(port->retry >= UPNP_MAX_RETRY) {
283 mapping.cookie = upnp_port_binding_retain(port);
284 mapping.local_port = port->local_port;
285 mapping.local_host = port->local_addr;
286 if(port->external_port == -1)
287 mapping.remote_port = rand()%1024 + 1024; // TODO: use better method
289 mapping.remote_port = port->external_port;
290 mapping.remote_host = "";
291 mapping.description = PACKAGE_NAME;
292 mapping.protocol = port->protocol;
295 ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
298 port->state = LinphoneUpnpStateKo;
303 int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *port) {
304 UpnpContext *lupnp = &lc->upnp;
305 upnp_igd_port_mapping mapping;
307 if(port->state == LinphoneUpnpStateIdle) {
309 port->state = LinphoneUpnpStateRemoving;
310 } else if(port->state != LinphoneUpnpStateRemoving) {
311 ms_error("uPnP: try to remove a port binding in wrong state: %d", port->state);
315 if(port->retry >= UPNP_MAX_RETRY) {
318 mapping.cookie = upnp_port_binding_retain(port);
319 mapping.remote_port = port->external_port;
320 mapping.remote_host = "";
321 mapping.protocol = port->protocol;
323 ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
326 port->state = LinphoneUpnpStateKo;
332 * uPnP Core interfaces
335 int upnp_call_process(LinphoneCall *call) {
336 LinphoneCore *lc = call->core;
337 UpnpContext *lupnp = &lc->upnp;
340 const char *local_addr, *external_addr;
342 ms_mutex_lock(&lupnp->mutex);
343 // Don't handle when the call
344 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
346 local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
347 external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
352 strncpy(call->upnp_session->audio->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
353 strncpy(call->upnp_session->audio->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
354 call->upnp_session->audio->rtp->local_port = call->audio_port;
355 strncpy(call->upnp_session->audio->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
356 strncpy(call->upnp_session->audio->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
357 call->upnp_session->audio->rtcp->local_port = call->audio_port+1;
358 if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle && call->audiostream != NULL) {
359 // Add audio port binding
360 upnp_context_send_add_port_binding(lc, call->upnp_session->audio->rtp);
361 } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk && call->audiostream == NULL) {
362 // Remove audio port binding
363 upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtp);
365 if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle && call->audiostream != NULL) {
366 // Add audio port binding
367 upnp_context_send_add_port_binding(lc, call->upnp_session->audio->rtcp);
368 } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk && call->audiostream == NULL) {
369 // Remove audio port binding
370 upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtcp);
372 if((call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle) &&
373 (call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle)) {
374 call->upnp_session->audio->state = LinphoneUpnpStateOk;
375 } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateAdding ||
376 call->upnp_session->audio->rtp->state == LinphoneUpnpStateRemoving ||
377 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateAdding ||
378 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateRemoving) {
379 call->upnp_session->audio->state = LinphoneUpnpStatePending;
380 } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateKo ||
381 call->upnp_session->audio->rtp->state == LinphoneUpnpStateKo) {
382 call->upnp_session->audio->state = LinphoneUpnpStateKo;
384 call->upnp_session->audio->state = LinphoneUpnpStateIdle;
390 strncpy(call->upnp_session->video->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
391 strncpy(call->upnp_session->video->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
392 call->upnp_session->video->rtp->local_port = call->video_port;
393 strncpy(call->upnp_session->video->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
394 strncpy(call->upnp_session->video->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
395 call->upnp_session->video->rtcp->local_port = call->video_port+1;
396 if(call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle && call->videostream != NULL) {
397 // Add video port binding
398 upnp_context_send_add_port_binding(lc, call->upnp_session->video->rtp);
399 } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateOk && call->videostream == NULL) {
400 // Remove video port binding
401 upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtp);
403 if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle && call->videostream != NULL) {
404 // Add video port binding
405 upnp_context_send_add_port_binding(lc, call->upnp_session->video->rtcp);
406 } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk && call->videostream == NULL) {
407 // Remove video port binding
408 upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtcp);
410 if((call->upnp_session->video->rtp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle) &&
411 (call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle)) {
412 call->upnp_session->video->state = LinphoneUpnpStateOk;
413 } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateAdding ||
414 call->upnp_session->video->rtp->state == LinphoneUpnpStateRemoving ||
415 call->upnp_session->video->rtcp->state == LinphoneUpnpStateAdding ||
416 call->upnp_session->video->rtcp->state == LinphoneUpnpStateRemoving) {
417 call->upnp_session->video->state = LinphoneUpnpStatePending;
418 } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateKo ||
419 call->upnp_session->video->rtp->state == LinphoneUpnpStateKo) {
420 call->upnp_session->video->state = LinphoneUpnpStateKo;
422 call->upnp_session->video->state = LinphoneUpnpStateIdle;
426 * Update session state
428 oldState = call->upnp_session->state;
429 if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
430 call->upnp_session->video->state == LinphoneUpnpStateOk) {
431 call->upnp_session->state = LinphoneUpnpStateOk;
432 } else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
433 call->upnp_session->video->state == LinphoneUpnpStatePending) {
434 call->upnp_session->state = LinphoneUpnpStatePending;
435 } else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
436 call->upnp_session->video->state == LinphoneUpnpStateKo) {
437 call->upnp_session->state = LinphoneUpnpStateKo;
439 call->upnp_session->state = LinphoneUpnpStateIdle;
442 /* When change is done proceed update */
443 if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
444 (call->upnp_session->state == LinphoneUpnpStateOk || call->upnp_session->state == LinphoneUpnpStateKo)) {
445 switch (call->state) {
446 case LinphoneCallUpdating:
447 linphone_core_start_update_call(call->core, call);
449 case LinphoneCallUpdatedByRemote:
450 linphone_core_start_accept_call_update(call->core, call);
452 case LinphoneCallOutgoingInit:
453 linphone_core_proceed_with_invite_if_ready(call->core, call, NULL);
455 case LinphoneCallIdle:
456 linphone_core_notify_incoming_call(call->core, call);
464 ms_mutex_unlock(&lupnp->mutex);
468 int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
469 return upnp_call_process(call);
472 bool_t linphone_core_upnp_hook(void *data) {
474 MSList *port_bindings = NULL;
475 MSList *port_bindings_item;
476 UpnpPortBinding *port_mapping;
477 LinphoneCore *lc = (LinphoneCore *)data;
478 UpnpContext *lupnp = &lc->upnp;
479 ms_mutex_lock(&lupnp->mutex);
481 if(lupnp->state == LinphoneUpnpStateOk && lupnp->old_state != LinphoneUpnpStateOk) {
482 // Remove old mapping
483 port_bindings = upnp_config_list_port_bindings(lc->config);
484 if(port_bindings != NULL) {
485 for(port_bindings_item = port_bindings;port_bindings_item!=NULL;port_bindings_item=port_bindings_item->next) {
486 port_mapping = (UpnpPortBinding *)port_bindings_item->data;
487 upnp_context_send_remove_port_binding(lc, port_mapping);
489 ms_list_for_each(port_bindings,(void (*)(void*))upnp_port_binding_release);
490 port_bindings = ms_list_free(port_bindings);
494 if(lupnp->state == LinphoneUpnpStateOk && lupnp->old_state != LinphoneUpnpStateOk) {
496 if(lupnp->sip_udp != NULL) {
497 upnp_context_send_add_port_binding(lc, lupnp->sip_udp);
499 if(lupnp->sip_tcp != NULL) {
500 upnp_context_send_add_port_binding(lc, lupnp->sip_tcp);
502 if(lupnp->sip_tls != NULL) {
503 upnp_context_send_add_port_binding(lc, lupnp->sip_tls);
508 for(port_bindings_item = lupnp->pending_configs;port_bindings_item!=NULL;port_bindings_item=port_bindings_item->next) {
509 port_mapping = (UpnpPortBinding *)port_bindings_item->data;
510 if(port_mapping->state == LinphoneUpnpStateAdding) {
511 snprintf(key, sizeof(key), "%s-%d-%d",
512 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
513 port_mapping->external_port,
514 port_mapping->local_port);
515 lp_config_set_string(lc->config, UPNP_SECTION_NAME, key, "");
517 if(port_mapping->state == LinphoneUpnpStateRemoving) {
518 snprintf(key, sizeof(key), "%s-%d-%d",
519 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
520 port_mapping->external_port,
521 port_mapping->local_port);
522 lp_config_set_string(lc->config, UPNP_SECTION_NAME, key, NULL);
525 ms_list_for_each(lupnp->pending_configs,(void (*)(void*))upnp_port_binding_release);
526 lupnp->pending_configs = ms_list_free(lupnp->pending_configs);
528 lupnp->old_state = lupnp->state;
529 ms_mutex_unlock(&lupnp->mutex);
533 void linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
535 SalStreamDescription *stream;
536 UpnpStream *upnpStream;
538 for (i = 0; i < desc->nstreams; i++) {
539 stream = &desc->streams[i];
541 if(stream->type == SalAudio) {
542 upnpStream = session->audio;
543 } else if(stream->type == SalVideo) {
544 upnpStream = session->video;
546 if(upnpStream != NULL) {
547 if(upnpStream->rtp->state == LinphoneUpnpStateOk) {
548 strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
549 stream->rtp_port = upnpStream->rtp->external_port;
551 if(upnpStream->rtcp->state == LinphoneUpnpStateOk) {
552 strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
553 stream->rtcp_port = upnpStream->rtcp->external_port;
564 UpnpPortBinding *upnp_port_binding_new() {
565 UpnpPortBinding *port = NULL;
566 port = ms_new0(UpnpPortBinding,1);
567 ms_mutex_init(&port->mutex, NULL);
568 port->state = LinphoneUpnpStateIdle;
569 port->local_addr[0] = '\0';
570 port->local_port = -1;
571 port->external_addr[0] = '\0';
572 port->external_port = -1;
577 UpnpPortBinding *upnp_port_binding_copy(const UpnpPortBinding *port) {
578 UpnpPortBinding *new_port = NULL;
579 new_port = ms_new0(UpnpPortBinding,1);
580 memcpy(new_port, port, sizeof(UpnpPortBinding));
581 ms_mutex_init(&new_port->mutex, NULL);
586 bool_t upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
587 return port1->protocol == port2->protocol && port1->local_port == port2->local_port &&
588 port1->external_port && port2->external_port;
591 UpnpPortBinding *upnp_port_binding_retain(UpnpPortBinding *port) {
592 ms_mutex_lock(&port->mutex);
594 ms_mutex_unlock(&port->mutex);
598 void upnp_port_binding_release(UpnpPortBinding *port) {
599 ms_mutex_lock(&port->mutex);
600 if(--port->ref == 0) {
601 ms_mutex_unlock(&port->mutex);
602 ms_mutex_destroy(&port->mutex);
606 ms_mutex_unlock(&port->mutex);
613 UpnpStream* upnp_stream_new() {
614 UpnpStream *stream = ms_new0(UpnpStream,1);
615 stream->state = LinphoneUpnpStateIdle;
616 stream->rtp = upnp_port_binding_new();
617 stream->rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
618 stream->rtcp = upnp_port_binding_new();
619 stream->rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
623 void upnp_stream_destroy(UpnpStream* stream) {
624 upnp_port_binding_release(stream->rtp);
625 upnp_port_binding_release(stream->rtcp);
634 UpnpSession* upnp_session_new() {
635 UpnpSession *session = ms_new0(UpnpSession,1);
636 session->state = LinphoneUpnpStateIdle;
637 session->audio = upnp_stream_new();
638 session->video = upnp_stream_new();
642 void upnp_session_destroy(LinphoneCall* call) {
643 LinphoneCore *lc = call->core;
645 /* Remove bindings */
646 if(call->upnp_session->audio->rtp->state != LinphoneUpnpStateKo && call->upnp_session->audio->rtp->state != LinphoneUpnpStateIdle) {
647 upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtp);
649 if(call->upnp_session->audio->rtcp->state != LinphoneUpnpStateKo && call->upnp_session->audio->rtcp->state != LinphoneUpnpStateIdle) {
650 upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtcp);
652 if(call->upnp_session->video->rtp->state != LinphoneUpnpStateKo && call->upnp_session->video->rtp->state != LinphoneUpnpStateIdle) {
653 upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtp);
655 if(call->upnp_session->video->rtcp->state != LinphoneUpnpStateKo && call->upnp_session->video->rtcp->state != LinphoneUpnpStateIdle) {
656 upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtcp);
659 upnp_stream_destroy(call->upnp_session->audio);
660 upnp_stream_destroy(call->upnp_session->video);
661 ms_free(call->upnp_session);
662 call->upnp_session = NULL;
670 MSList *upnp_config_list_port_bindings(struct _LpConfig *lpc) {
671 char protocol_str[4]; // TCP or UDP
672 upnp_igd_ip_protocol protocol;
675 MSList *retList = NULL;
676 UpnpPortBinding *port;
681 LpSection *sec=lp_config_find_section(lpc, UPNP_SECTION_NAME);
686 while(elem != NULL) {
687 item=(LpItem*)elem->data;
689 if(sscanf(item->key, "%3s-%i-%i", protocol_str, &external_port, &local_port) == 3) {
690 if(strcasecmp(protocol_str, "TCP") == 0) {
691 protocol = UPNP_IGD_IP_PROTOCOL_TCP;
692 } else if(strcasecmp(protocol_str, "UDP") == 0) {
693 protocol = UPNP_IGD_IP_PROTOCOL_UDP;
698 port = upnp_port_binding_new();
699 port->protocol = protocol;
700 port->external_port = external_port;
701 port->local_port = local_port;
702 retList = ms_list_append(retList, port);
708 elem = ms_list_next(elem);
710 ms_warning("uPnP configuration invalid line: %s", item->key);
711 lp_section_remove_item(sec, item);
718 int upnp_config_add_port_binding(LinphoneCore *lc, const UpnpPortBinding *port) {
719 UpnpContext *lupnp = &lc->upnp;
720 MSList *list = lupnp->pending_configs;
721 UpnpPortBinding *list_port;
724 while(list != NULL) {
726 list_port = (UpnpPortBinding *)list->data;
727 if(upnp_port_binding_equal(list_port, port) == TRUE) {
728 if(list_port->state == LinphoneUpnpStateAdding) {
732 if(list_port->state == LinphoneUpnpStateRemoving) {
737 list = ms_list_next(list);
741 lupnp->pending_configs = ms_list_remove(list, list_port);
743 list_port = upnp_port_binding_copy(port);
744 list_port->state = LinphoneUpnpStateAdding;
745 lupnp->pending_configs = ms_list_append(list, list_port);
751 int upnp_config_remove_port_binding(LinphoneCore *lc, const UpnpPortBinding *port) {
752 UpnpContext *lupnp = &lc->upnp;
753 MSList *list = lupnp->pending_configs;
754 UpnpPortBinding *list_port;
757 while(list != NULL) {
759 list_port = (UpnpPortBinding *)list->data;
760 if(upnp_port_binding_equal(list_port, port)) {
761 if(list_port->state == LinphoneUpnpStateRemoving) {
765 if(list_port->state == LinphoneUpnpStateAdding) {
770 list = ms_list_next(list);
774 lupnp->pending_configs = ms_list_remove(list, list_port);
776 list_port = upnp_port_binding_copy(port);
777 list_port->state = LinphoneUpnpStateRemoving;
778 lupnp->pending_configs = ms_list_append(list, list_port);