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_log(int level, const char *msg, const UpnpPortBinding *port);
57 void upnp_port_binding_release(UpnpPortBinding *port);
59 MSList *upnp_config_list_port_bindings(struct _LpConfig *lpc);
60 int upnp_config_add_port_binding(LinphoneCore *lc, const UpnpPortBinding *port);
61 int upnp_config_remove_port_binding(LinphoneCore *lc, const UpnpPortBinding *port);
63 int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *port);
64 int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port);
71 /* Convert uPnP IGD logs to ortp logs */
72 void linphone_upnp_igd_print(void *cookie, upnp_igd_print_level level, const char *fmt, va_list list) {
73 int ortp_level = ORTP_DEBUG;
75 case UPNP_IGD_MESSAGE:
76 ortp_level = ORTP_MESSAGE;
78 case UPNP_IGD_WARNING:
79 ortp_level = ORTP_DEBUG; // Too verbose otherwise
82 ortp_level = ORTP_DEBUG; // Too verbose otherwise
87 ortp_logv(ortp_level, fmt, list);
90 void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) {
91 LinphoneCore *lc = (LinphoneCore *)cookie;
92 UpnpContext *lupnp = &lc->upnp;
93 upnp_igd_port_mapping *mapping = NULL;
94 UpnpPortBinding *port_mapping = NULL;
95 const char *ip_address = NULL;
96 const char *connection_status = NULL;
97 bool_t nat_enabled = FALSE;
98 ms_mutex_lock(&lupnp->mutex);
101 case UPNP_IGD_EXTERNAL_IPADDRESS_CHANGED:
102 case UPNP_IGD_NAT_ENABLED_CHANGED:
103 case UPNP_IGD_CONNECTION_STATUS_CHANGED:
104 ip_address = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
105 connection_status = upnp_igd_get_connection_status(lupnp->upnp_igd_ctxt);
106 nat_enabled = upnp_igd_get_nat_enabled(lupnp->upnp_igd_ctxt);
108 if(ip_address == NULL || connection_status == NULL) {
109 ms_message("uPnP IGD: Pending");
110 lupnp->state = LinphoneUpnpStatePending;
111 } else if(strcasecmp(connection_status, "Connected") || !nat_enabled) {
112 ms_message("uPnP IGD: Not Available");
113 lupnp->state = LinphoneUpnpStateNotAvailable;
115 ms_message("uPnP IGD: Connected");
116 lupnp->state = LinphoneUpnpStateOk;
121 case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS:
122 mapping = (upnp_igd_port_mapping *) arg;
123 port_mapping = (UpnpPortBinding*) mapping->cookie;
124 port_mapping->external_port = mapping->remote_port;
125 port_mapping->state = LinphoneUpnpStateOk;
126 upnp_port_binding_log(ORTP_MESSAGE, "Added port binding", port_mapping);
127 upnp_config_add_port_binding(lc, port_mapping);
129 upnp_port_binding_release(port_mapping);
132 case UPNP_IGD_PORT_MAPPING_ADD_FAILURE:
133 mapping = (upnp_igd_port_mapping *) arg;
134 port_mapping = (UpnpPortBinding*) mapping->cookie;
135 if(upnp_context_send_add_port_binding(lc, port_mapping) != 0) {
136 upnp_port_binding_log(ORTP_ERROR, "Can't add port binding", port_mapping);
139 upnp_port_binding_release(port_mapping);
142 case UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS:
143 mapping = (upnp_igd_port_mapping *) arg;
144 port_mapping = (UpnpPortBinding*) mapping->cookie;
145 port_mapping->state = LinphoneUpnpStateIdle;
146 upnp_port_binding_log(ORTP_MESSAGE, "Removed port binding", port_mapping);
147 upnp_config_remove_port_binding(lc, port_mapping);
149 upnp_port_binding_release(port_mapping);
152 case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE:
153 mapping = (upnp_igd_port_mapping *) arg;
154 port_mapping = (UpnpPortBinding*) mapping->cookie;
155 if(upnp_context_send_remove_port_binding(lc, port_mapping) != 0) {
156 upnp_port_binding_log(ORTP_ERROR, "Can't remove port binding", port_mapping);
157 upnp_config_remove_port_binding(lc, port_mapping);
160 upnp_port_binding_release(port_mapping);
167 ms_mutex_unlock(&lupnp->mutex);
175 int upnp_context_init(LinphoneCore *lc) {
176 LCSipTransports transport;
177 UpnpContext *lupnp = &lc->upnp;
178 const char *ip_address;
180 ms_mutex_init(&lupnp->mutex, NULL);
181 lupnp->pending_configs = NULL;
182 lupnp->state = LinphoneUpnpStateIdle;
183 lupnp->old_state = LinphoneUpnpStateIdle;
184 ms_message("uPnP IGD: Init");
186 linphone_core_get_sip_transports(lc, &transport);
187 if(transport.udp_port != 0) {
188 lupnp->sip_udp = upnp_port_binding_new();
189 lupnp->sip_udp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
190 lupnp->sip_udp->local_port = transport.udp_port;
192 lupnp->sip_udp = NULL;
194 if(transport.tcp_port != 0) {
195 lupnp->sip_tcp = upnp_port_binding_new();
196 lupnp->sip_tcp->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
197 lupnp->sip_tcp->local_port = transport.tcp_port;
199 lupnp->sip_tcp = NULL;
201 if(transport.tls_port != 0) {
202 lupnp->sip_tls = upnp_port_binding_new();
203 lupnp->sip_tls->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
204 lupnp->sip_tls->local_port = transport.tls_port;
206 lupnp->sip_tls = NULL;
209 linphone_core_add_iterate_hook(lc, linphone_core_upnp_hook, lc);
211 lupnp->upnp_igd_ctxt = NULL;
212 lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, lc);
213 if(lupnp->upnp_igd_ctxt == NULL) {
214 lupnp->state = LinphoneUpnpStateKo;
215 ms_error("Can't create uPnP IGD context");
219 ip_address = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
220 if(lupnp->sip_udp != NULL) {
221 strncpy(lupnp->sip_udp->local_addr, ip_address, sizeof(lupnp->sip_udp->local_addr));
223 if(lupnp->sip_tcp != NULL) {
224 strncpy(lupnp->sip_tcp->local_addr, ip_address, sizeof(lupnp->sip_tcp->local_addr));
226 if(lupnp->sip_tls != NULL) {
227 strncpy(lupnp->sip_tls->local_addr, ip_address, sizeof(lupnp->sip_tls->local_addr));
230 lupnp->state = LinphoneUpnpStatePending;
234 void upnp_context_uninit(LinphoneCore *lc) {
235 UpnpContext *lupnp = &lc->upnp;
236 linphone_core_remove_iterate_hook(lc, linphone_core_upnp_hook, lc);
238 if(lupnp->sip_udp != NULL) {
239 upnp_port_binding_release(lupnp->sip_udp);
241 if(lupnp->sip_tcp != NULL) {
242 upnp_port_binding_release(lupnp->sip_tcp);
244 if(lupnp->sip_tls != NULL) {
245 upnp_port_binding_release(lupnp->sip_tls);
247 if(lupnp->upnp_igd_ctxt != NULL) {
248 upnp_igd_destroy(lupnp->upnp_igd_ctxt);
250 ms_mutex_destroy(&lupnp->mutex);
252 ms_message("uPnP IGD: Uninit");
255 int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port) {
256 UpnpContext *lupnp = &lc->upnp;
257 upnp_igd_port_mapping mapping;
259 if(port->state == LinphoneUpnpStateIdle) {
260 port->external_port = -1;
262 port->state = LinphoneUpnpStateAdding;
263 } else if(port->state != LinphoneUpnpStateAdding) {
264 ms_error("uPnP: try to add a port binding in wrong state: %d", port->state);
268 if(port->retry >= UPNP_MAX_RETRY) {
271 mapping.cookie = upnp_port_binding_retain(port);
272 mapping.local_port = port->local_port;
273 mapping.local_host = port->local_addr;
274 if(port->external_port == -1)
275 mapping.remote_port = rand()%1024 + 1024; // TODO: use better method
277 mapping.remote_port = port->external_port;
278 mapping.remote_host = "";
279 mapping.description = PACKAGE_NAME;
280 mapping.protocol = port->protocol;
283 ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
286 port->state = LinphoneUpnpStateKo;
291 int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *port) {
292 UpnpContext *lupnp = &lc->upnp;
293 upnp_igd_port_mapping mapping;
295 if(port->state == LinphoneUpnpStateOk) {
297 port->state = LinphoneUpnpStateRemoving;
298 } else if(port->state != LinphoneUpnpStateRemoving) {
299 ms_error("uPnP: try to remove a port binding in wrong state: %d", port->state);
303 if(port->retry >= UPNP_MAX_RETRY) {
306 mapping.cookie = upnp_port_binding_retain(port);
307 mapping.remote_port = port->external_port;
308 mapping.remote_host = "";
309 mapping.protocol = port->protocol;
311 ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
314 port->state = LinphoneUpnpStateKo;
320 * uPnP Core interfaces
323 int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool_t video) {
324 LinphoneCore *lc = call->core;
325 UpnpContext *lupnp = &lc->upnp;
327 const char *local_addr, *external_addr;
329 ms_mutex_lock(&lupnp->mutex);
330 // Don't handle when the call
331 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
333 local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
334 external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
339 strncpy(call->upnp_session->audio->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
340 strncpy(call->upnp_session->audio->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
341 call->upnp_session->audio->rtp->local_port = call->audio_port;
342 strncpy(call->upnp_session->audio->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
343 strncpy(call->upnp_session->audio->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
344 call->upnp_session->audio->rtcp->local_port = call->audio_port+1;
345 if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle && audio) {
346 // Add audio port binding
347 upnp_context_send_add_port_binding(lc, call->upnp_session->audio->rtp);
348 } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk && !audio) {
349 // Remove audio port binding
350 upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtp);
352 if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle && audio) {
353 // Add audio port binding
354 upnp_context_send_add_port_binding(lc, call->upnp_session->audio->rtcp);
355 } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk && !audio) {
356 // Remove audio port binding
357 upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtcp);
363 strncpy(call->upnp_session->video->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
364 strncpy(call->upnp_session->video->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
365 call->upnp_session->video->rtp->local_port = call->video_port;
366 strncpy(call->upnp_session->video->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
367 strncpy(call->upnp_session->video->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
368 call->upnp_session->video->rtcp->local_port = call->video_port+1;
369 if(call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle && video) {
370 // Add video port binding
371 upnp_context_send_add_port_binding(lc, call->upnp_session->video->rtp);
372 } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateOk && !video) {
373 // Remove video port binding
374 upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtp);
376 if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle && video) {
377 // Add video port binding
378 upnp_context_send_add_port_binding(lc, call->upnp_session->video->rtcp);
379 } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk && !video) {
380 // Remove video port binding
381 upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtcp);
385 ms_mutex_unlock(&lupnp->mutex);
390 int linphone_core_update_upnp_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) {
391 bool_t audio = FALSE;
392 bool_t video = FALSE;
394 const SalStreamDescription *stream;
396 for (i = 0; i < md->nstreams; i++) {
397 stream = &md->streams[i];
398 if(stream->type == SalAudio) {
400 } else if(stream->type == SalVideo) {
405 return linphone_core_update_upnp_audio_video(call, audio, video);
408 int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
409 return linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
412 int upnp_call_process(LinphoneCall *call) {
413 LinphoneCore *lc = call->core;
414 UpnpContext *lupnp = &lc->upnp;
418 ms_mutex_lock(&lupnp->mutex);
420 // Don't handle when the call
421 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
427 if((call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle) &&
428 (call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle)) {
429 call->upnp_session->audio->state = LinphoneUpnpStateOk;
430 } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateAdding ||
431 call->upnp_session->audio->rtp->state == LinphoneUpnpStateRemoving ||
432 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateAdding ||
433 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateRemoving) {
434 call->upnp_session->audio->state = LinphoneUpnpStatePending;
435 } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateKo ||
436 call->upnp_session->audio->rtp->state == LinphoneUpnpStateKo) {
437 call->upnp_session->audio->state = LinphoneUpnpStateKo;
439 call->upnp_session->audio->state = LinphoneUpnpStateIdle;
445 if((call->upnp_session->video->rtp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle) &&
446 (call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle)) {
447 call->upnp_session->video->state = LinphoneUpnpStateOk;
448 } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateAdding ||
449 call->upnp_session->video->rtp->state == LinphoneUpnpStateRemoving ||
450 call->upnp_session->video->rtcp->state == LinphoneUpnpStateAdding ||
451 call->upnp_session->video->rtcp->state == LinphoneUpnpStateRemoving) {
452 call->upnp_session->video->state = LinphoneUpnpStatePending;
453 } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateKo ||
454 call->upnp_session->video->rtp->state == LinphoneUpnpStateKo) {
455 call->upnp_session->video->state = LinphoneUpnpStateKo;
457 call->upnp_session->video->state = LinphoneUpnpStateIdle;
461 * Update session state
463 oldState = call->upnp_session->state;
464 if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
465 call->upnp_session->video->state == LinphoneUpnpStateOk) {
466 call->upnp_session->state = LinphoneUpnpStateOk;
467 } else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
468 call->upnp_session->video->state == LinphoneUpnpStatePending) {
469 call->upnp_session->state = LinphoneUpnpStatePending;
470 } else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
471 call->upnp_session->video->state == LinphoneUpnpStateKo) {
472 call->upnp_session->state = LinphoneUpnpStateKo;
474 call->upnp_session->state = LinphoneUpnpStateIdle;
477 /* When change is done proceed update */
478 if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
479 (call->upnp_session->state == LinphoneUpnpStateOk || call->upnp_session->state == LinphoneUpnpStateKo)) {
480 if(call->upnp_session->state == LinphoneUpnpStateOk)
481 ms_message("uPnP IGD: uPnP for Call %p is ok", call);
483 ms_message("uPnP IGD: uPnP for Call %p is ko", call);
485 switch (call->state) {
486 case LinphoneCallUpdating:
487 linphone_core_start_update_call(lc, call);
489 case LinphoneCallUpdatedByRemote:
490 linphone_core_start_accept_call_update(lc, call);
492 case LinphoneCallOutgoingInit:
493 linphone_core_proceed_with_invite_if_ready(lc, call, NULL);
495 case LinphoneCallIdle:
496 linphone_core_notify_incoming_call(lc, call);
504 ms_mutex_unlock(&lupnp->mutex);
508 bool_t linphone_core_upnp_hook(void *data) {
510 MSList *port_bindings = NULL;
511 MSList *port_bindings_item;
512 UpnpPortBinding *port_mapping;
513 LinphoneCore *lc = (LinphoneCore *)data;
514 UpnpContext *lupnp = &lc->upnp;
515 ms_mutex_lock(&lupnp->mutex);
517 if(lupnp->state == LinphoneUpnpStateOk && lupnp->old_state != LinphoneUpnpStateOk) {
518 // Remove old mapping
519 port_bindings = upnp_config_list_port_bindings(lc->config);
520 if(port_bindings != NULL) {
521 for(port_bindings_item = port_bindings;port_bindings_item!=NULL;port_bindings_item=port_bindings_item->next) {
522 port_mapping = (UpnpPortBinding *)port_bindings_item->data;
523 upnp_context_send_remove_port_binding(lc, port_mapping);
525 ms_list_for_each(port_bindings,(void (*)(void*))upnp_port_binding_release);
526 port_bindings = ms_list_free(port_bindings);
530 if(lupnp->state == LinphoneUpnpStateOk && lupnp->old_state != LinphoneUpnpStateOk) {
532 if(lupnp->sip_udp != NULL) {
533 upnp_context_send_add_port_binding(lc, lupnp->sip_udp);
535 if(lupnp->sip_tcp != NULL) {
536 upnp_context_send_add_port_binding(lc, lupnp->sip_tcp);
538 if(lupnp->sip_tls != NULL) {
539 upnp_context_send_add_port_binding(lc, lupnp->sip_tls);
544 for(port_bindings_item = lupnp->pending_configs;port_bindings_item!=NULL;port_bindings_item=port_bindings_item->next) {
545 port_mapping = (UpnpPortBinding *)port_bindings_item->data;
546 if(port_mapping->state == LinphoneUpnpStateAdding) {
547 snprintf(key, sizeof(key), "%s-%d-%d",
548 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
549 port_mapping->external_port,
550 port_mapping->local_port);
551 lp_config_set_string(lc->config, UPNP_SECTION_NAME, key, "uPnP");
552 upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping);
554 if(port_mapping->state == LinphoneUpnpStateRemoving) {
555 snprintf(key, sizeof(key), "%s-%d-%d",
556 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
557 port_mapping->external_port,
558 port_mapping->local_port);
559 lp_config_set_string(lc->config, UPNP_SECTION_NAME, key, NULL);
560 upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping);
563 ms_list_for_each(lupnp->pending_configs,(void (*)(void*))upnp_port_binding_release);
564 lupnp->pending_configs = ms_list_free(lupnp->pending_configs);
566 lupnp->old_state = lupnp->state;
567 ms_mutex_unlock(&lupnp->mutex);
571 void linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
573 SalStreamDescription *stream;
574 UpnpStream *upnpStream;
576 for (i = 0; i < desc->nstreams; i++) {
577 stream = &desc->streams[i];
579 if(stream->type == SalAudio) {
580 upnpStream = session->audio;
581 } else if(stream->type == SalVideo) {
582 upnpStream = session->video;
584 if(upnpStream != NULL) {
585 if(upnpStream->rtp->state == LinphoneUpnpStateOk) {
586 strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
587 stream->rtp_port = upnpStream->rtp->external_port;
589 if(upnpStream->rtcp->state == LinphoneUpnpStateOk) {
590 strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
591 stream->rtcp_port = upnpStream->rtcp->external_port;
602 UpnpPortBinding *upnp_port_binding_new() {
603 UpnpPortBinding *port = NULL;
604 port = ms_new0(UpnpPortBinding,1);
605 ms_mutex_init(&port->mutex, NULL);
606 port->state = LinphoneUpnpStateIdle;
607 port->local_addr[0] = '\0';
608 port->local_port = -1;
609 port->external_addr[0] = '\0';
610 port->external_port = -1;
615 UpnpPortBinding *upnp_port_binding_copy(const UpnpPortBinding *port) {
616 UpnpPortBinding *new_port = NULL;
617 new_port = ms_new0(UpnpPortBinding,1);
618 memcpy(new_port, port, sizeof(UpnpPortBinding));
619 ms_mutex_init(&new_port->mutex, NULL);
624 void upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port) {
625 if(strlen(port->local_addr)) {
626 ortp_log(level, "uPnP IGD: %s %s|%d->%s:%d", msg,
627 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
632 ortp_log(level, "uPnP IGD: %s %s|%d->%d", msg,
633 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
639 bool_t upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
640 return port1->protocol == port2->protocol &&
641 port1->local_port == port2->local_port &&
642 port1->external_port == port2->external_port;
645 UpnpPortBinding *upnp_port_binding_retain(UpnpPortBinding *port) {
646 ms_mutex_lock(&port->mutex);
648 ms_mutex_unlock(&port->mutex);
652 void upnp_port_binding_release(UpnpPortBinding *port) {
653 ms_mutex_lock(&port->mutex);
654 if(--port->ref == 0) {
655 ms_mutex_unlock(&port->mutex);
656 ms_mutex_destroy(&port->mutex);
660 ms_mutex_unlock(&port->mutex);
668 UpnpStream* upnp_stream_new() {
669 UpnpStream *stream = ms_new0(UpnpStream,1);
670 stream->state = LinphoneUpnpStateIdle;
671 stream->rtp = upnp_port_binding_new();
672 stream->rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
673 stream->rtcp = upnp_port_binding_new();
674 stream->rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
678 void upnp_stream_destroy(UpnpStream* stream) {
679 upnp_port_binding_release(stream->rtp);
680 upnp_port_binding_release(stream->rtcp);
689 UpnpSession* upnp_session_new() {
690 UpnpSession *session = ms_new0(UpnpSession,1);
691 session->state = LinphoneUpnpStateIdle;
692 session->audio = upnp_stream_new();
693 session->video = upnp_stream_new();
697 void upnp_session_destroy(LinphoneCall* call) {
698 LinphoneCore *lc = call->core;
700 /* Remove bindings */
701 if(call->upnp_session->audio->rtp->state != LinphoneUpnpStateKo && call->upnp_session->audio->rtp->state != LinphoneUpnpStateIdle) {
702 upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtp);
704 if(call->upnp_session->audio->rtcp->state != LinphoneUpnpStateKo && call->upnp_session->audio->rtcp->state != LinphoneUpnpStateIdle) {
705 upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtcp);
707 if(call->upnp_session->video->rtp->state != LinphoneUpnpStateKo && call->upnp_session->video->rtp->state != LinphoneUpnpStateIdle) {
708 upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtp);
710 if(call->upnp_session->video->rtcp->state != LinphoneUpnpStateKo && call->upnp_session->video->rtcp->state != LinphoneUpnpStateIdle) {
711 upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtcp);
714 upnp_stream_destroy(call->upnp_session->audio);
715 upnp_stream_destroy(call->upnp_session->video);
716 ms_free(call->upnp_session);
717 call->upnp_session = NULL;
725 MSList *upnp_config_list_port_bindings(struct _LpConfig *lpc) {
726 char protocol_str[4]; // TCP or UDP
727 upnp_igd_ip_protocol protocol;
730 MSList *retList = NULL;
731 UpnpPortBinding *port;
736 LpSection *sec=lp_config_find_section(lpc, UPNP_SECTION_NAME);
741 while(elem != NULL) {
742 item=(LpItem*)elem->data;
744 if(sscanf(item->key, "%3s-%i-%i", protocol_str, &external_port, &local_port) == 3) {
745 if(strcasecmp(protocol_str, "TCP") == 0) {
746 protocol = UPNP_IGD_IP_PROTOCOL_TCP;
747 } else if(strcasecmp(protocol_str, "UDP") == 0) {
748 protocol = UPNP_IGD_IP_PROTOCOL_UDP;
753 port = upnp_port_binding_new();
754 port->state = LinphoneUpnpStateOk;
755 port->protocol = protocol;
756 port->external_port = external_port;
757 port->local_port = local_port;
758 retList = ms_list_append(retList, port);
764 elem = ms_list_next(elem);
766 ms_warning("uPnP configuration invalid line: %s", item->key);
767 lp_section_remove_item(sec, item);
774 int upnp_config_add_port_binding(LinphoneCore *lc, const UpnpPortBinding *port) {
775 UpnpContext *lupnp = &lc->upnp;
776 MSList *list = lupnp->pending_configs;
777 UpnpPortBinding *list_port;
778 bool_t remove = FALSE;
780 while(list != NULL) {
781 list_port = (UpnpPortBinding *)list->data;
782 if(upnp_port_binding_equal(list_port, port) == TRUE) {
783 if(list_port->state == LinphoneUpnpStateAdding) {
787 if(list_port->state == LinphoneUpnpStateRemoving) {
792 list = ms_list_next(list);
796 lupnp->pending_configs = ms_list_remove(list, list_port);
798 list_port = upnp_port_binding_copy(port);
799 list_port->state = LinphoneUpnpStateAdding;
800 lupnp->pending_configs = ms_list_append(list, list_port);
806 int upnp_config_remove_port_binding(LinphoneCore *lc, const UpnpPortBinding *port) {
807 UpnpContext *lupnp = &lc->upnp;
808 MSList *list = lupnp->pending_configs;
809 UpnpPortBinding *list_port;
810 bool_t remove = FALSE;
812 while(list != NULL) {
813 list_port = (UpnpPortBinding *)list->data;
814 if(upnp_port_binding_equal(list_port, port)) {
815 if(list_port->state == LinphoneUpnpStateRemoving) {
819 if(list_port->state == LinphoneUpnpStateAdding) {
824 list = ms_list_next(list);
828 lupnp->pending_configs = ms_list_remove(list, list_port);
830 list_port = upnp_port_binding_copy(port);
831 list_port->state = LinphoneUpnpStateRemoving;
832 lupnp->pending_configs = ms_list_append(list, list_port);