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.
24 #define UPNP_ADD_MAX_RETRY 4
25 #define UPNP_REMOVE_MAX_RETRY 4
26 #define UPNP_SECTION_NAME "uPnP"
27 #define UPNP_CORE_RETRY_DELAY 4
28 #define UPNP_CALL_RETRY_DELAY 1
34 typedef struct _UpnpPortBinding {
36 LinphoneUpnpState state;
37 upnp_igd_ip_protocol protocol;
38 char local_addr[LINPHONE_IPADDR_SIZE];
40 char external_addr[LINPHONE_IPADDR_SIZE];
49 typedef struct _UpnpStream {
51 UpnpPortBinding *rtcp;
52 LinphoneUpnpState state;
59 LinphoneUpnpState state;
64 upnp_igd_context *upnp_igd_ctxt;
65 UpnpPortBinding *sip_tcp;
66 UpnpPortBinding *sip_tls;
67 UpnpPortBinding *sip_udp;
68 LinphoneUpnpState state;
69 MSList *removing_configs;
70 MSList *adding_configs;
71 MSList *pending_bindings;
79 bool_t linphone_core_upnp_hook(void *data);
80 void linphone_core_upnp_refresh(UpnpContext *ctx);
82 UpnpPortBinding *linphone_upnp_port_binding_new();
83 UpnpPortBinding *linphone_upnp_port_binding_new_with_parameters(upnp_igd_ip_protocol protocol, int local_port, int external_port);
84 UpnpPortBinding *linphone_upnp_port_binding_new_or_collect(MSList *list, upnp_igd_ip_protocol protocol, int local_port, int external_port);
85 UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port);
86 bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2);
87 UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port);
88 UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port);
89 void linphone_upnp_update_port_binding(UpnpContext *lupnp, UpnpPortBinding **port_mapping, upnp_igd_ip_protocol protocol, int port, int retry_delay);
90 void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port);
91 void linphone_upnp_port_binding_release(UpnpPortBinding *port);
94 MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc);
95 void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port);
96 void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port);
99 int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry);
100 int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry);
107 /* Convert uPnP IGD logs to ortp logs */
108 void linphone_upnp_igd_print(void *cookie, upnp_igd_print_level level, const char *fmt, va_list list) {
109 int ortp_level = ORTP_DEBUG;
111 case UPNP_IGD_MESSAGE:
112 ortp_level = ORTP_MESSAGE;
114 case UPNP_IGD_WARNING:
115 ortp_level = ORTP_DEBUG; // Too verbose otherwise
118 ortp_level = ORTP_DEBUG; // Too verbose otherwise
123 ortp_logv(ortp_level, fmt, list);
126 void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) {
127 UpnpContext *lupnp = (UpnpContext *)cookie;
128 upnp_igd_port_mapping *mapping = NULL;
129 UpnpPortBinding *port_mapping = NULL;
130 const char *ip_address = NULL;
131 const char *connection_status = NULL;
132 bool_t nat_enabled = FALSE;
133 LinphoneUpnpState old_state;
135 if(lupnp == NULL || lupnp->upnp_igd_ctxt == NULL) {
136 ms_error("uPnP IGD: Invalid context in callback");
140 ms_mutex_lock(&lupnp->mutex);
141 old_state = lupnp->state;
144 case UPNP_IGD_DEVICE_ADDED:
145 case UPNP_IGD_DEVICE_REMOVED:
146 case UPNP_IGD_EXTERNAL_IPADDRESS_CHANGED:
147 case UPNP_IGD_NAT_ENABLED_CHANGED:
148 case UPNP_IGD_CONNECTION_STATUS_CHANGED:
149 ip_address = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
150 connection_status = upnp_igd_get_connection_status(lupnp->upnp_igd_ctxt);
151 nat_enabled = upnp_igd_get_nat_enabled(lupnp->upnp_igd_ctxt);
153 if(ip_address == NULL || connection_status == NULL) {
154 ms_message("uPnP IGD: Pending");
155 lupnp->state = LinphoneUpnpStatePending;
156 } else if(strcasecmp(connection_status, "Connected") || !nat_enabled) {
157 ms_message("uPnP IGD: Not Available");
158 lupnp->state = LinphoneUpnpStateNotAvailable;
160 ms_message("uPnP IGD: Connected");
161 lupnp->state = LinphoneUpnpStateOk;
162 if(old_state != LinphoneUpnpStateOk) {
163 linphone_core_upnp_refresh(lupnp);
169 case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS:
170 mapping = (upnp_igd_port_mapping *) arg;
171 port_mapping = (UpnpPortBinding*) mapping->cookie;
172 port_mapping->external_port = mapping->remote_port;
173 port_mapping->state = LinphoneUpnpStateOk;
174 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Added port binding", port_mapping);
175 linphone_upnp_config_add_port_binding(lupnp, port_mapping);
179 case UPNP_IGD_PORT_MAPPING_ADD_FAILURE:
180 mapping = (upnp_igd_port_mapping *) arg;
181 port_mapping = (UpnpPortBinding*) mapping->cookie;
182 port_mapping->external_port = -1; //Force random external port
183 if(linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, TRUE) != 0) {
184 linphone_upnp_port_binding_log(ORTP_ERROR, "Can't add port binding", port_mapping);
189 case UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS:
190 mapping = (upnp_igd_port_mapping *) arg;
191 port_mapping = (UpnpPortBinding*) mapping->cookie;
192 port_mapping->state = LinphoneUpnpStateIdle;
193 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Removed port binding", port_mapping);
194 linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
198 case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE:
199 mapping = (upnp_igd_port_mapping *) arg;
200 port_mapping = (UpnpPortBinding*) mapping->cookie;
201 if(linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE) != 0) {
202 linphone_upnp_port_binding_log(ORTP_ERROR, "Can't remove port binding", port_mapping);
203 linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
212 if(port_mapping != NULL) {
214 * Execute delayed actions
216 if(port_mapping->to_remove) {
217 if(port_mapping->state == LinphoneUpnpStateOk) {
218 port_mapping->to_remove = FALSE;
219 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, FALSE);
220 } else if(port_mapping->state == LinphoneUpnpStateKo) {
221 port_mapping->to_remove = FALSE;
224 if(port_mapping->to_add) {
225 if(port_mapping->state == LinphoneUpnpStateIdle || port_mapping->state == LinphoneUpnpStateKo) {
226 port_mapping->to_add = FALSE;
227 linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, FALSE);
231 lupnp->pending_bindings = ms_list_remove(lupnp->pending_bindings, port_mapping);
232 linphone_upnp_port_binding_release(port_mapping);
236 * If there is no pending binding emit a signal
238 if(lupnp->pending_bindings == NULL) {
239 pthread_cond_signal(&lupnp->empty_cond);
241 ms_mutex_unlock(&lupnp->mutex);
249 UpnpContext* linphone_upnp_context_new(LinphoneCore *lc) {
250 UpnpContext *lupnp = (UpnpContext *)ms_new0(UpnpContext,1);
252 ms_mutex_init(&lupnp->mutex, NULL);
253 ms_cond_init(&lupnp->empty_cond, NULL);
256 lupnp->pending_bindings = NULL;
257 lupnp->adding_configs = NULL;
258 lupnp->removing_configs = NULL;
259 lupnp->state = LinphoneUpnpStateIdle;
260 ms_message("uPnP IGD: New %p for core %p", lupnp, lc);
263 lupnp->sip_udp = NULL;
264 lupnp->sip_tcp = NULL;
265 lupnp->sip_tls = NULL;
267 linphone_core_add_iterate_hook(lc, linphone_core_upnp_hook, lupnp);
269 lupnp->upnp_igd_ctxt = NULL;
270 lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, lupnp);
271 if(lupnp->upnp_igd_ctxt == NULL) {
272 lupnp->state = LinphoneUpnpStateKo;
273 ms_error("Can't create uPnP IGD context");
277 lupnp->state = LinphoneUpnpStatePending;
278 upnp_igd_start(lupnp->upnp_igd_ctxt);
283 void linphone_upnp_context_destroy(UpnpContext *lupnp) {
284 linphone_core_remove_iterate_hook(lupnp->lc, linphone_core_upnp_hook, lupnp);
286 ms_mutex_lock(&lupnp->mutex);
288 /* Send port binding removes */
289 if(lupnp->sip_udp != NULL) {
290 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_udp, TRUE);
292 if(lupnp->sip_tcp != NULL) {
293 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tcp, TRUE);
295 if(lupnp->sip_tls != NULL) {
296 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tls, TRUE);
299 /* Wait all pending bindings are done */
300 if(lupnp->pending_bindings != NULL) {
301 ms_message("uPnP IGD: Wait all pending port bindings ...");
302 ms_cond_wait(&lupnp->empty_cond, &lupnp->mutex);
304 ms_mutex_unlock(&lupnp->mutex);
306 if(lupnp->upnp_igd_ctxt != NULL) {
307 upnp_igd_destroy(lupnp->upnp_igd_ctxt);
310 /* Run one time the hook for configuration update */
311 linphone_core_upnp_hook(lupnp);
313 /* Release port bindings */
314 if(lupnp->sip_udp != NULL) {
315 linphone_upnp_port_binding_release(lupnp->sip_udp);
316 lupnp->sip_udp = NULL;
318 if(lupnp->sip_tcp != NULL) {
319 linphone_upnp_port_binding_release(lupnp->sip_tcp);
320 lupnp->sip_tcp = NULL;
322 if(lupnp->sip_tls != NULL) {
323 linphone_upnp_port_binding_release(lupnp->sip_tls);
324 lupnp->sip_tcp = NULL;
328 ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
329 lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
330 ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
331 lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
332 ms_list_for_each(lupnp->pending_bindings,(void (*)(void*))linphone_upnp_port_binding_release);
333 lupnp->pending_bindings = ms_list_free(lupnp->pending_bindings);
335 ms_mutex_destroy(&lupnp->mutex);
336 ms_cond_destroy(&lupnp->empty_cond);
338 ms_message("uPnP IGD: destroy %p", lupnp);
342 LinphoneUpnpState linphone_upnp_context_get_state(UpnpContext *lupnp) {
343 LinphoneUpnpState state;
344 ms_mutex_lock(&lupnp->mutex);
345 state = lupnp->state;
346 ms_mutex_unlock(&lupnp->mutex);
350 bool_t linphone_upnp_context_is_ready_for_register(UpnpContext *lupnp) {
352 ms_mutex_lock(&lupnp->mutex);
354 // 1 Check global uPnP state
355 ready = (lupnp->state == LinphoneUpnpStateOk);
357 // 2 Check external ip address
358 if(ready && upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt) == NULL) {
362 // 3 Check sip ports bindings
364 if(lupnp->sip_udp != NULL) {
365 if(lupnp->sip_udp->state != LinphoneUpnpStateOk) {
368 } else if(lupnp->sip_tcp != NULL) {
369 if(lupnp->sip_tcp->state != LinphoneUpnpStateOk) {
372 } else if(lupnp->sip_tls != NULL) {
373 if(lupnp->sip_tls->state != LinphoneUpnpStateOk) {
381 ms_mutex_unlock(&lupnp->mutex);
385 int linphone_upnp_context_get_external_port(UpnpContext *lupnp) {
387 ms_mutex_lock(&lupnp->mutex);
389 if(lupnp->sip_udp != NULL) {
390 if(lupnp->sip_udp->state == LinphoneUpnpStateOk) {
391 port = lupnp->sip_udp->external_port;
393 } else if(lupnp->sip_tcp != NULL) {
394 if(lupnp->sip_tcp->state == LinphoneUpnpStateOk) {
395 port = lupnp->sip_tcp->external_port;
397 } else if(lupnp->sip_tls != NULL) {
398 if(lupnp->sip_tls->state == LinphoneUpnpStateOk) {
399 port = lupnp->sip_tls->external_port;
403 ms_mutex_unlock(&lupnp->mutex);
407 const char* linphone_upnp_context_get_external_ipaddress(UpnpContext *lupnp) {
408 const char* addr = NULL;
409 ms_mutex_lock(&lupnp->mutex);
410 addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
411 ms_mutex_unlock(&lupnp->mutex);
415 int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry) {
416 upnp_igd_port_mapping mapping;
417 char description[128];
420 if(lupnp->state != LinphoneUpnpStateOk) {
424 // Compute port binding state
425 if(port->state != LinphoneUpnpStateAdding) {
426 port->to_remove = FALSE;
427 switch(port->state) {
428 case LinphoneUpnpStateKo:
429 case LinphoneUpnpStateIdle: {
431 port->state = LinphoneUpnpStateAdding;
434 case LinphoneUpnpStateRemoving: {
444 // No retry if specified
445 if(port->retry != 0 && !retry) {
449 if(port->retry >= UPNP_ADD_MAX_RETRY) {
452 mapping.cookie = linphone_upnp_port_binding_retain(port);
453 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
455 mapping.local_port = port->local_port;
456 mapping.local_host = port->local_addr;
457 if(port->external_port == -1)
458 mapping.remote_port = rand()%(0xffff - 1024) + 1024;
460 mapping.remote_port = port->external_port;
461 mapping.remote_host = "";
462 snprintf(description, 128, "%s %s at %s:%d",
464 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP": "UDP",
465 port->local_addr, port->local_port);
466 mapping.description = description;
467 mapping.protocol = port->protocol;
470 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to add port binding", port);
471 ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
474 port->state = LinphoneUpnpStateKo;
479 int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry) {
480 upnp_igd_port_mapping mapping;
483 if(lupnp->state != LinphoneUpnpStateOk) {
487 // Compute port binding state
488 if(port->state != LinphoneUpnpStateRemoving) {
489 port->to_add = FALSE;
490 switch(port->state) {
491 case LinphoneUpnpStateOk: {
493 port->state = LinphoneUpnpStateRemoving;
496 case LinphoneUpnpStateAdding: {
497 port->to_remove = TRUE;
506 // No retry if specified
507 if(port->retry != 0 && !retry) {
511 if(port->retry >= UPNP_REMOVE_MAX_RETRY) {
514 mapping.cookie = linphone_upnp_port_binding_retain(port);
515 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
517 mapping.remote_port = port->external_port;
518 mapping.remote_host = "";
519 mapping.protocol = port->protocol;
521 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to remove port binding", port);
522 ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
525 port->state = LinphoneUpnpStateKo;
531 * uPnP Core interfaces
534 int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool_t video) {
535 LinphoneCore *lc = call->core;
536 UpnpContext *lupnp = lc->upnp;
543 ms_mutex_lock(&lupnp->mutex);
544 // Don't handle when the call
545 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
551 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->audio->rtp,
552 UPNP_IGD_IP_PROTOCOL_UDP, (audio)? call->audio_port:0, UPNP_CALL_RETRY_DELAY);
554 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->audio->rtcp,
555 UPNP_IGD_IP_PROTOCOL_UDP, (audio)? call->audio_port+1:0, UPNP_CALL_RETRY_DELAY);
560 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->video->rtp,
561 UPNP_IGD_IP_PROTOCOL_UDP, (video)? call->video_port:0, UPNP_CALL_RETRY_DELAY);
563 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->video->rtcp,
564 UPNP_IGD_IP_PROTOCOL_UDP, (video)? call->video_port+1:0, UPNP_CALL_RETRY_DELAY);
567 ms_mutex_unlock(&lupnp->mutex);
570 * Update uPnP call state
572 linphone_upnp_call_process(call);
579 int linphone_core_update_upnp_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) {
580 bool_t audio = FALSE;
581 bool_t video = FALSE;
583 const SalStreamDescription *stream;
585 for (i = 0; i < md->n_total_streams; i++) {
586 stream = &md->streams[i];
587 if(stream->type == SalAudio) {
589 } else if(stream->type == SalVideo) {
594 return linphone_core_update_upnp_audio_video(call, audio, video);
597 int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
598 return linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
601 void linphone_core_update_upnp_state_in_call_stats(LinphoneCall *call) {
602 call->stats[LINPHONE_CALL_STATS_AUDIO].upnp_state = call->upnp_session->audio->state;
603 call->stats[LINPHONE_CALL_STATS_VIDEO].upnp_state = call->upnp_session->video->state;
606 void linphone_upnp_update_stream_state(UpnpStream *stream) {
607 if((stream->rtp == NULL || stream->rtp->state == LinphoneUpnpStateOk || stream->rtp->state == LinphoneUpnpStateIdle) &&
608 (stream->rtcp == NULL || stream->rtcp->state == LinphoneUpnpStateOk || stream->rtcp->state == LinphoneUpnpStateIdle)) {
609 stream->state = LinphoneUpnpStateOk;
610 } else if((stream->rtp != NULL &&
611 (stream->rtp->state == LinphoneUpnpStateAdding || stream->rtp->state == LinphoneUpnpStateRemoving)) ||
612 (stream->rtcp != NULL &&
613 (stream->rtcp->state == LinphoneUpnpStateAdding || stream->rtcp->state == LinphoneUpnpStateRemoving))) {
614 stream->state = LinphoneUpnpStatePending;
615 } else if((stream->rtp != NULL && stream->rtp->state == LinphoneUpnpStateKo) ||
616 (stream->rtcp != NULL && stream->rtcp->state == LinphoneUpnpStateKo)) {
617 stream->state = LinphoneUpnpStateKo;
619 ms_error("Invalid stream %p state", stream);
623 int linphone_upnp_call_process(LinphoneCall *call) {
624 LinphoneCore *lc = call->core;
625 UpnpContext *lupnp = lc->upnp;
627 LinphoneUpnpState oldState = 0, newState = 0;
633 ms_mutex_lock(&lupnp->mutex);
635 // Don't handle when the call
636 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
642 linphone_upnp_update_stream_state(call->upnp_session->audio);
647 linphone_upnp_update_stream_state(call->upnp_session->video);
650 * Update session state
652 oldState = call->upnp_session->state;
653 if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
654 call->upnp_session->video->state == LinphoneUpnpStateOk) {
655 call->upnp_session->state = LinphoneUpnpStateOk;
656 } else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
657 call->upnp_session->video->state == LinphoneUpnpStatePending) {
658 call->upnp_session->state = LinphoneUpnpStatePending;
659 } else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
660 call->upnp_session->video->state == LinphoneUpnpStateKo) {
661 call->upnp_session->state = LinphoneUpnpStateKo;
663 call->upnp_session->state = LinphoneUpnpStateIdle;
665 newState = call->upnp_session->state;
667 /* When change is done proceed update */
668 if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
669 (call->upnp_session->state == LinphoneUpnpStateOk || call->upnp_session->state == LinphoneUpnpStateKo)) {
670 if(call->upnp_session->state == LinphoneUpnpStateOk)
671 ms_message("uPnP IGD: uPnP for Call %p is ok", call);
673 ms_message("uPnP IGD: uPnP for Call %p is ko", call);
675 switch (call->state) {
676 case LinphoneCallUpdating:
677 linphone_core_start_update_call(lc, call);
679 case LinphoneCallUpdatedByRemote:
680 linphone_core_start_accept_call_update(lc, call);
682 case LinphoneCallOutgoingInit:
683 linphone_core_proceed_with_invite_if_ready(lc, call, NULL);
685 case LinphoneCallIdle:
686 linphone_core_notify_incoming_call(lc, call);
694 ms_mutex_unlock(&lupnp->mutex);
697 * Update uPnP call stats
699 if(oldState != newState) {
700 linphone_core_update_upnp_state_in_call_stats(call);
706 void linphone_core_upnp_refresh(UpnpContext *lupnp) {
707 MSList *global_list = NULL;
711 UpnpPortBinding *port_mapping, *port_mapping2;
713 ms_message("uPnP IGD: Refresh mappings");
715 if(lupnp->sip_udp != NULL) {
716 global_list = ms_list_append(global_list, lupnp->sip_udp);
718 if(lupnp->sip_tcp != NULL) {
719 global_list = ms_list_append(global_list, lupnp->sip_tcp);
721 if(lupnp->sip_tls != NULL) {
722 global_list = ms_list_append(global_list, lupnp->sip_tls);
725 list = lupnp->lc->calls;
726 while(list != NULL) {
727 call = (LinphoneCall *)list->data;
728 if(call->upnp_session != NULL) {
729 if(call->upnp_session->audio->rtp != NULL) {
730 global_list = ms_list_append(global_list, call->upnp_session->audio->rtp);
732 if(call->upnp_session->audio->rtcp != NULL) {
733 global_list = ms_list_append(global_list, call->upnp_session->audio->rtcp);
735 if(call->upnp_session->video->rtp != NULL) {
736 global_list = ms_list_append(global_list, call->upnp_session->video->rtp);
738 if(call->upnp_session->video->rtcp != NULL) {
739 global_list = ms_list_append(global_list, call->upnp_session->video->rtcp);
745 list = linphone_upnp_config_list_port_bindings(lupnp->lc->config);
746 for(item = list;item != NULL; item = item->next) {
747 port_mapping = (UpnpPortBinding *)item->data;
748 port_mapping2 = linphone_upnp_port_binding_equivalent_in_list(global_list, port_mapping);
749 if(port_mapping2 == NULL) {
750 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE);
751 } else if(port_mapping2->state == LinphoneUpnpStateIdle){
752 /* Force to remove */
753 port_mapping2->state = LinphoneUpnpStateOk;
756 ms_list_for_each(list, (void (*)(void*))linphone_upnp_port_binding_release);
757 list = ms_list_free(list);
760 // (Re)Add removed port bindings
762 while(list != NULL) {
763 port_mapping = (UpnpPortBinding *)list->data;
764 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE);
765 linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, TRUE);
768 global_list = ms_list_free(global_list);
771 void linphone_upnp_update_port_binding(UpnpContext *lupnp, UpnpPortBinding **port_mapping, upnp_igd_ip_protocol protocol, int port, int retry_delay) {
772 const char *local_addr, *external_addr;
773 time_t now = time(NULL);
775 if(*port_mapping != NULL) {
776 if(port != (*port_mapping)->local_port) {
777 linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
778 *port_mapping = NULL;
781 if(*port_mapping == NULL) {
782 *port_mapping = linphone_upnp_port_binding_new_or_collect(lupnp->pending_bindings, protocol, port, port);
786 local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
787 external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
789 // Force binding update on local address change
790 if(local_addr != NULL) {
791 if(strncmp((*port_mapping)->local_addr, local_addr, sizeof((*port_mapping)->local_addr))) {
792 linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
793 strncpy((*port_mapping)->local_addr, local_addr, sizeof((*port_mapping)->local_addr));
796 ms_warning("uPnP IGD: can't get local address");
798 if(external_addr != NULL) {
799 strncpy((*port_mapping)->external_addr, external_addr, sizeof((*port_mapping)->external_addr));
801 ms_warning("uPnP IGD: can't get external address");
804 // Add (if not already done) the binding
805 if(now - (*port_mapping)->last_update >= retry_delay) {
806 (*port_mapping)->last_update = now;
807 linphone_upnp_context_send_add_port_binding(lupnp, *port_mapping, FALSE);
810 if(*port_mapping != NULL) {
811 linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
812 *port_mapping = NULL;
817 bool_t linphone_core_upnp_hook(void *data) {
819 LCSipTransports transport;
821 UpnpPortBinding *port_mapping;
822 UpnpContext *lupnp = (UpnpContext *)data;
824 ms_mutex_lock(&lupnp->mutex);
827 if(lupnp->state == LinphoneUpnpStateOk) {
828 linphone_core_get_sip_transports(lupnp->lc, &transport);
829 linphone_upnp_update_port_binding(lupnp, &lupnp->sip_udp, UPNP_IGD_IP_PROTOCOL_UDP, transport.udp_port, UPNP_CORE_RETRY_DELAY);
830 linphone_upnp_update_port_binding(lupnp, &lupnp->sip_tcp, UPNP_IGD_IP_PROTOCOL_TCP, transport.tcp_port, UPNP_CORE_RETRY_DELAY);
831 linphone_upnp_update_port_binding(lupnp, &lupnp->sip_tls, UPNP_IGD_IP_PROTOCOL_TCP, transport.tls_port, UPNP_CORE_RETRY_DELAY);
835 for(item = lupnp->adding_configs;item!=NULL;item=item->next) {
836 port_mapping = (UpnpPortBinding *)item->data;
837 snprintf(key, sizeof(key), "%s-%d-%d",
838 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
839 port_mapping->external_port,
840 port_mapping->local_port);
841 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, "uPnP");
842 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping);
844 ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
845 lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
848 for(item = lupnp->removing_configs;item!=NULL;item=item->next) {
849 port_mapping = (UpnpPortBinding *)item->data;
850 snprintf(key, sizeof(key), "%s-%d-%d",
851 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
852 port_mapping->external_port,
853 port_mapping->local_port);
854 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, NULL);
855 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping);
857 ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
858 lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
860 ms_mutex_unlock(&lupnp->mutex);
864 int linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
866 SalStreamDescription *stream;
867 UpnpStream *upnpStream;
869 for (i = 0; i < desc->n_active_streams; i++) {
870 stream = &desc->streams[i];
872 if(stream->type == SalAudio) {
873 upnpStream = session->audio;
874 } else if(stream->type == SalVideo) {
875 upnpStream = session->video;
877 if(upnpStream != NULL) {
878 if(upnpStream->rtp != NULL && upnpStream->rtp->state == LinphoneUpnpStateOk) {
879 strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
880 stream->rtp_port = upnpStream->rtp->external_port;
882 if(upnpStream->rtcp != NULL && upnpStream->rtcp->state == LinphoneUpnpStateOk) {
883 strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
884 stream->rtcp_port = upnpStream->rtcp->external_port;
896 UpnpPortBinding *linphone_upnp_port_binding_new() {
897 UpnpPortBinding *port = NULL;
898 port = ms_new0(UpnpPortBinding,1);
899 ms_mutex_init(&port->mutex, NULL);
900 port->state = LinphoneUpnpStateIdle;
901 port->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
902 port->local_addr[0] = '\0';
903 port->local_port = -1;
904 port->external_addr[0] = '\0';
905 port->external_port = -1;
906 port->to_remove = FALSE;
907 port->to_add = FALSE;
909 port->last_update = 0;
913 UpnpPortBinding *linphone_upnp_port_binding_new_with_parameters(upnp_igd_ip_protocol protocol, int local_port, int external_port) {
914 UpnpPortBinding *port_binding = linphone_upnp_port_binding_new();
915 port_binding->protocol = protocol;
916 port_binding->local_port = local_port;
917 port_binding->external_port = external_port;
921 UpnpPortBinding *linphone_upnp_port_binding_new_or_collect(MSList *list, upnp_igd_ip_protocol protocol, int local_port, int external_port) {
922 UpnpPortBinding *tmp_binding;
923 UpnpPortBinding *end_binding;
924 end_binding = linphone_upnp_port_binding_new_with_parameters(protocol, local_port, external_port);
925 tmp_binding = linphone_upnp_port_binding_equivalent_in_list(list, end_binding);
926 if(tmp_binding != NULL) {
927 linphone_upnp_port_binding_release(end_binding);
928 end_binding = tmp_binding;
933 UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port) {
934 UpnpPortBinding *new_port = NULL;
935 new_port = ms_new0(UpnpPortBinding,1);
936 memcpy(new_port, port, sizeof(UpnpPortBinding));
937 ms_mutex_init(&new_port->mutex, NULL);
942 void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port) {
943 if(strlen(port->local_addr)) {
944 ortp_log(level, "uPnP IGD: %s %s|%d->%s:%d (retry %d)", msg,
945 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
951 ortp_log(level, "uPnP IGD: %s %s|%d->%d (retry %d)", msg,
952 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
959 bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
960 return port1->protocol == port2->protocol &&
961 port1->local_port == port2->local_port &&
962 port1->external_port == port2->external_port;
965 UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port) {
966 UpnpPortBinding *port_mapping;
967 while(list != NULL) {
968 port_mapping = (UpnpPortBinding *)list->data;
969 if(linphone_upnp_port_binding_equal(port, port_mapping)) {
978 UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port) {
979 ms_mutex_lock(&port->mutex);
981 ms_mutex_unlock(&port->mutex);
985 void linphone_upnp_port_binding_release(UpnpPortBinding *port) {
986 ms_mutex_lock(&port->mutex);
987 if(--port->ref == 0) {
988 ms_mutex_unlock(&port->mutex);
989 ms_mutex_destroy(&port->mutex);
993 ms_mutex_unlock(&port->mutex);
1001 UpnpStream* linphone_upnp_stream_new() {
1002 UpnpStream *stream = ms_new0(UpnpStream,1);
1003 stream->state = LinphoneUpnpStateIdle;
1005 stream->rtcp = NULL;
1009 void linphone_upnp_stream_destroy(UpnpStream* stream) {
1010 if(stream->rtp != NULL) {
1011 linphone_upnp_port_binding_release(stream->rtp);
1014 if(stream->rtcp != NULL) {
1015 linphone_upnp_port_binding_release(stream->rtcp);
1016 stream->rtcp = NULL;
1026 UpnpSession* linphone_upnp_session_new(LinphoneCall* call) {
1027 UpnpSession *session = ms_new0(UpnpSession,1);
1028 session->call = call;
1029 session->state = LinphoneUpnpStateIdle;
1030 session->audio = linphone_upnp_stream_new();
1031 session->video = linphone_upnp_stream_new();
1035 void linphone_upnp_session_destroy(UpnpSession *session) {
1036 LinphoneCore *lc = session->call->core;
1038 if(lc->upnp != NULL) {
1039 /* Remove bindings */
1040 if(session->audio->rtp != NULL) {
1041 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtp, TRUE);
1043 if(session->audio->rtcp != NULL) {
1044 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtcp, TRUE);
1046 if(session->video->rtp != NULL) {
1047 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtp, TRUE);
1049 if(session->video->rtcp != NULL) {
1050 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtcp, TRUE);
1054 linphone_upnp_stream_destroy(session->audio);
1055 linphone_upnp_stream_destroy(session->video);
1059 LinphoneUpnpState linphone_upnp_session_get_state(UpnpSession *session) {
1060 return session->state;
1068 struct linphone_upnp_config_list_port_bindings_struct {
1069 struct _LpConfig *lpc;
1073 static void linphone_upnp_config_list_port_bindings_cb(const char *entry, struct linphone_upnp_config_list_port_bindings_struct *cookie) {
1074 char protocol_str[4]; // TCP or UDP
1075 upnp_igd_ip_protocol protocol;
1078 bool_t valid = TRUE;
1079 UpnpPortBinding *port;
1080 if(sscanf(entry, "%3s-%i-%i", protocol_str, &external_port, &local_port) == 3) {
1081 if(strcasecmp(protocol_str, "TCP") == 0) {
1082 protocol = UPNP_IGD_IP_PROTOCOL_TCP;
1083 } else if(strcasecmp(protocol_str, "UDP") == 0) {
1084 protocol = UPNP_IGD_IP_PROTOCOL_UDP;
1089 port = linphone_upnp_port_binding_new();
1090 port->state = LinphoneUpnpStateOk;
1091 port->protocol = protocol;
1092 port->external_port = external_port;
1093 port->local_port = local_port;
1094 cookie->retList = ms_list_append(cookie->retList, port);
1100 ms_warning("uPnP configuration invalid line: %s", entry);
1104 MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc) {
1105 struct linphone_upnp_config_list_port_bindings_struct cookie = {lpc, NULL};
1106 lp_config_for_each_entry(lpc, UPNP_SECTION_NAME, (void(*)(const char *, void*))linphone_upnp_config_list_port_bindings_cb, &cookie);
1107 return cookie.retList;
1110 void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1112 UpnpPortBinding *list_port;
1114 list = lupnp->removing_configs;
1115 while(list != NULL) {
1116 list_port = (UpnpPortBinding *)list->data;
1117 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1118 lupnp->removing_configs = ms_list_remove(lupnp->removing_configs, list_port);
1119 linphone_upnp_port_binding_release(list_port);
1122 list = ms_list_next(list);
1125 list = lupnp->adding_configs;
1126 while(list != NULL) {
1127 list_port = (UpnpPortBinding *)list->data;
1128 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1131 list = ms_list_next(list);
1134 list_port = linphone_upnp_port_binding_copy(port);
1135 lupnp->adding_configs = ms_list_append(lupnp->adding_configs, list_port);
1138 void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1140 UpnpPortBinding *list_port;
1142 list = lupnp->adding_configs;
1143 while(list != NULL) {
1144 list_port = (UpnpPortBinding *)list->data;
1145 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1146 lupnp->adding_configs = ms_list_remove(lupnp->adding_configs, list_port);
1147 linphone_upnp_port_binding_release(list_port);
1150 list = ms_list_next(list);
1153 list = lupnp->removing_configs;
1154 while(list != NULL) {
1155 list_port = (UpnpPortBinding *)list->data;
1156 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1159 list = ms_list_next(list);
1162 list_port = linphone_upnp_port_binding_copy(port);
1163 lupnp->removing_configs = ms_list_append(lupnp->removing_configs, list_port);