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.
25 #define UPNP_STRINGIFY(x) #x
26 #define UPNP_TOSTRING(x) UPNP_STRINGIFY(x)
28 #define UPNP_ADD_MAX_RETRY 4
29 #define UPNP_REMOVE_MAX_RETRY 4
30 #define UPNP_SECTION_NAME "uPnP"
31 #define UPNP_CORE_READY_CHECK 1
32 #define UPNP_CORE_RETRY_DELAY 4
33 #define UPNP_CALL_RETRY_DELAY 1
34 #define UPNP_UUID_LEN 128
35 #define UPNP_UUID_LEN_STR UPNP_TOSTRING(UPNP_UUID_LEN)
40 typedef struct _UpnpPortBinding {
42 LinphoneUpnpState state;
43 upnp_igd_ip_protocol protocol;
45 char local_addr[LINPHONE_IPADDR_SIZE];
47 char external_addr[LINPHONE_IPADDR_SIZE];
56 typedef struct _UpnpStream {
58 UpnpPortBinding *rtcp;
59 LinphoneUpnpState state;
66 LinphoneUpnpState state;
71 upnp_igd_context *upnp_igd_ctxt;
72 UpnpPortBinding *sip_tcp;
73 UpnpPortBinding *sip_tls;
74 UpnpPortBinding *sip_udp;
75 LinphoneUpnpState state;
76 MSList *removing_configs;
77 MSList *adding_configs;
78 MSList *pending_bindings;
83 time_t last_ready_check;
84 LinphoneUpnpState last_ready_state;
88 bool_t linphone_core_upnp_hook(void *data);
89 void linphone_core_upnp_refresh(UpnpContext *ctx);
91 UpnpPortBinding *linphone_upnp_port_binding_new();
92 UpnpPortBinding *linphone_upnp_port_binding_new_with_parameters(upnp_igd_ip_protocol protocol, int local_port, int external_port);
93 UpnpPortBinding *linphone_upnp_port_binding_new_or_collect(MSList *list, upnp_igd_ip_protocol protocol, int local_port, int external_port);
94 UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port);
95 void linphone_upnp_port_binding_set_device_id(UpnpPortBinding *port, const char * device_id);
96 bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2);
97 UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port);
98 UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port);
99 void linphone_upnp_update_port_binding(UpnpContext *lupnp, UpnpPortBinding **port_mapping, upnp_igd_ip_protocol protocol, int port, int retry_delay);
100 void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port);
101 void linphone_upnp_port_binding_release(UpnpPortBinding *port);
102 void linphone_upnp_update_config(UpnpContext *lupnp);
103 void linphone_upnp_update_proxy(UpnpContext *lupnp, bool_t force);
106 MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc, const char *device_id);
107 void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port);
108 void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port);
111 int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry);
112 int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry);
114 static int linphone_upnp_strncmpi(const char *str1, const char *str2, int len) {
118 char1 = toupper(*str1);
119 char2 = toupper(*str2);
120 if(char1 == '\0' || char1 != char2) {
121 return char1 - char2;
130 static int linphone_upnp_str_min(const char *str1, const char *str2) {
131 int len1 = strlen(str1);
132 int len2 = strlen(str2);
139 char * linphone_upnp_format_device_id(const char *device_id) {
144 if(device_id == NULL) {
147 ret = ms_new(char, UPNP_UUID_LEN + 1);
149 if(linphone_upnp_strncmpi(device_id, "uuid:", linphone_upnp_str_min(device_id, "uuid:")) == 0) {
150 device_id += strlen("uuid:");
152 while(*device_id != '\0' && tmp - ret < UPNP_UUID_LEN) {
155 if(tchar >= '0' && tchar <= '9')
157 if(!copy && tchar >= 'A' && tchar <= 'Z')
159 if(!copy && tchar >= 'a' && tchar <= 'z')
175 /* Convert uPnP IGD logs to ortp logs */
176 void linphone_upnp_igd_print(void *cookie, upnp_igd_print_level level, const char *fmt, va_list list) {
177 int ortp_level = ORTP_DEBUG;
179 case UPNP_IGD_MESSAGE:
180 ortp_level = ORTP_MESSAGE;
182 case UPNP_IGD_WARNING:
183 ortp_level = ORTP_DEBUG; // Too verbose otherwise
186 ortp_level = ORTP_DEBUG; // Too verbose otherwise
191 ortp_logv(ortp_level, fmt, list);
194 void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) {
195 UpnpContext *lupnp = (UpnpContext *)cookie;
196 upnp_igd_port_mapping *mapping = NULL;
197 UpnpPortBinding *port_mapping = NULL;
198 const char *ip_address = NULL;
199 const char *connection_status = NULL;
200 bool_t nat_enabled = FALSE;
201 LinphoneUpnpState old_state;
203 if(lupnp == NULL || lupnp->upnp_igd_ctxt == NULL) {
204 ms_error("uPnP IGD: Invalid context in callback");
208 ms_mutex_lock(&lupnp->mutex);
209 old_state = lupnp->state;
212 case UPNP_IGD_DEVICE_ADDED:
213 case UPNP_IGD_DEVICE_REMOVED:
214 case UPNP_IGD_EXTERNAL_IPADDRESS_CHANGED:
215 case UPNP_IGD_NAT_ENABLED_CHANGED:
216 case UPNP_IGD_CONNECTION_STATUS_CHANGED:
217 ip_address = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
218 connection_status = upnp_igd_get_connection_status(lupnp->upnp_igd_ctxt);
219 nat_enabled = upnp_igd_get_nat_enabled(lupnp->upnp_igd_ctxt);
221 if(ip_address == NULL || connection_status == NULL) {
222 ms_message("uPnP IGD: Pending");
223 lupnp->state = LinphoneUpnpStatePending;
224 } else if(strcasecmp(connection_status, "Connected") || !nat_enabled) {
225 ms_message("uPnP IGD: Not Available");
226 lupnp->state = LinphoneUpnpStateNotAvailable;
228 ms_message("uPnP IGD: Connected");
229 lupnp->state = LinphoneUpnpStateOk;
230 if(old_state != LinphoneUpnpStateOk) {
231 linphone_core_upnp_refresh(lupnp);
237 case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS:
238 mapping = (upnp_igd_port_mapping *) arg;
239 port_mapping = (UpnpPortBinding*) mapping->cookie;
240 port_mapping->external_port = mapping->remote_port;
241 port_mapping->state = LinphoneUpnpStateOk;
242 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Added port binding", port_mapping);
243 linphone_upnp_config_add_port_binding(lupnp, port_mapping);
247 case UPNP_IGD_PORT_MAPPING_ADD_FAILURE:
248 mapping = (upnp_igd_port_mapping *) arg;
249 port_mapping = (UpnpPortBinding*) mapping->cookie;
250 port_mapping->external_port = -1; //Force random external port
251 if(linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, TRUE) != 0) {
252 linphone_upnp_port_binding_log(ORTP_ERROR, "Can't add port binding", port_mapping);
257 case UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS:
258 mapping = (upnp_igd_port_mapping *) arg;
259 port_mapping = (UpnpPortBinding*) mapping->cookie;
260 port_mapping->state = LinphoneUpnpStateIdle;
261 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Removed port binding", port_mapping);
262 linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
266 case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE:
267 mapping = (upnp_igd_port_mapping *) arg;
268 port_mapping = (UpnpPortBinding*) mapping->cookie;
269 if(linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE) != 0) {
270 linphone_upnp_port_binding_log(ORTP_ERROR, "Can't remove port binding", port_mapping);
271 linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
280 if(port_mapping != NULL) {
282 * Execute delayed actions
284 if(port_mapping->to_remove) {
285 if(port_mapping->state == LinphoneUpnpStateOk) {
286 port_mapping->to_remove = FALSE;
287 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, FALSE);
288 } else if(port_mapping->state == LinphoneUpnpStateKo) {
289 port_mapping->to_remove = FALSE;
292 if(port_mapping->to_add) {
293 if(port_mapping->state == LinphoneUpnpStateIdle || port_mapping->state == LinphoneUpnpStateKo) {
294 port_mapping->to_add = FALSE;
295 linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, FALSE);
299 lupnp->pending_bindings = ms_list_remove(lupnp->pending_bindings, port_mapping);
300 linphone_upnp_port_binding_release(port_mapping);
304 * If there is no pending binding emit a signal
306 if(lupnp->pending_bindings == NULL) {
307 ms_cond_signal(&lupnp->empty_cond);
309 ms_mutex_unlock(&lupnp->mutex);
317 UpnpContext* linphone_upnp_context_new(LinphoneCore *lc) {
318 UpnpContext *lupnp = (UpnpContext *)ms_new0(UpnpContext,1);
320 ms_mutex_init(&lupnp->mutex, NULL);
321 ms_cond_init(&lupnp->empty_cond, NULL);
323 lupnp->last_ready_check = 0;
324 lupnp->last_ready_state = LinphoneUpnpStateIdle;
327 lupnp->pending_bindings = NULL;
328 lupnp->adding_configs = NULL;
329 lupnp->removing_configs = NULL;
330 lupnp->state = LinphoneUpnpStateIdle;
331 ms_message("uPnP IGD: New %p for core %p", lupnp, lc);
334 lupnp->sip_udp = NULL;
335 lupnp->sip_tcp = NULL;
336 lupnp->sip_tls = NULL;
338 linphone_core_add_iterate_hook(lc, linphone_core_upnp_hook, lupnp);
340 lupnp->upnp_igd_ctxt = NULL;
341 lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, NULL, lupnp);
342 if(lupnp->upnp_igd_ctxt == NULL) {
343 lupnp->state = LinphoneUpnpStateKo;
344 ms_error("Can't create uPnP IGD context");
348 lupnp->state = LinphoneUpnpStatePending;
349 upnp_igd_start(lupnp->upnp_igd_ctxt);
354 void linphone_upnp_context_destroy(UpnpContext *lupnp) {
355 linphone_core_remove_iterate_hook(lupnp->lc, linphone_core_upnp_hook, lupnp);
357 ms_mutex_lock(&lupnp->mutex);
359 if(lupnp->lc->network_reachable) {
360 /* Send port binding removes */
361 if(lupnp->sip_udp != NULL) {
362 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_udp, TRUE);
364 if(lupnp->sip_tcp != NULL) {
365 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tcp, TRUE);
367 if(lupnp->sip_tls != NULL) {
368 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tls, TRUE);
372 /* Wait all pending bindings are done */
373 if(lupnp->pending_bindings != NULL) {
374 ms_message("uPnP IGD: Wait all pending port bindings ...");
375 ms_cond_wait(&lupnp->empty_cond, &lupnp->mutex);
377 ms_mutex_unlock(&lupnp->mutex);
379 if(lupnp->upnp_igd_ctxt != NULL) {
380 upnp_igd_destroy(lupnp->upnp_igd_ctxt);
381 lupnp->upnp_igd_ctxt = NULL;
384 /* No more multi threading here */
386 /* Run one more time configuration update and proxy */
387 linphone_upnp_update_config(lupnp);
388 linphone_upnp_update_proxy(lupnp, TRUE);
390 /* Release port bindings */
391 if(lupnp->sip_udp != NULL) {
392 linphone_upnp_port_binding_release(lupnp->sip_udp);
393 lupnp->sip_udp = NULL;
395 if(lupnp->sip_tcp != NULL) {
396 linphone_upnp_port_binding_release(lupnp->sip_tcp);
397 lupnp->sip_tcp = NULL;
399 if(lupnp->sip_tls != NULL) {
400 linphone_upnp_port_binding_release(lupnp->sip_tls);
401 lupnp->sip_tcp = NULL;
405 ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
406 lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
407 ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
408 lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
409 ms_list_for_each(lupnp->pending_bindings,(void (*)(void*))linphone_upnp_port_binding_release);
410 lupnp->pending_bindings = ms_list_free(lupnp->pending_bindings);
412 ms_mutex_destroy(&lupnp->mutex);
413 ms_cond_destroy(&lupnp->empty_cond);
415 ms_message("uPnP IGD: destroy %p", lupnp);
419 LinphoneUpnpState linphone_upnp_context_get_state(UpnpContext *lupnp) {
420 LinphoneUpnpState state = LinphoneUpnpStateKo;
422 ms_mutex_lock(&lupnp->mutex);
423 state = lupnp->state;
424 ms_mutex_unlock(&lupnp->mutex);
429 bool_t _linphone_upnp_context_is_ready_for_register(UpnpContext *lupnp) {
432 // 1 Check global uPnP state
433 ready = (lupnp->state == LinphoneUpnpStateOk);
435 // 2 Check external ip address
437 if (upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt) == NULL) {
442 // 3 Check sip ports bindings
444 if(lupnp->sip_udp != NULL) {
445 if(lupnp->sip_udp->state != LinphoneUpnpStateOk) {
448 } else if(lupnp->sip_tcp != NULL) {
449 if(lupnp->sip_tcp->state != LinphoneUpnpStateOk) {
452 } else if(lupnp->sip_tls != NULL) {
453 if(lupnp->sip_tls->state != LinphoneUpnpStateOk) {
464 bool_t linphone_upnp_context_is_ready_for_register(UpnpContext *lupnp) {
465 bool_t ready = FALSE;
467 ms_mutex_lock(&lupnp->mutex);
468 ready = _linphone_upnp_context_is_ready_for_register(lupnp);
469 ms_mutex_unlock(&lupnp->mutex);
474 int linphone_upnp_context_get_external_port(UpnpContext *lupnp) {
477 ms_mutex_lock(&lupnp->mutex);
479 if(lupnp->sip_udp != NULL) {
480 if(lupnp->sip_udp->state == LinphoneUpnpStateOk) {
481 port = lupnp->sip_udp->external_port;
483 } else if(lupnp->sip_tcp != NULL) {
484 if(lupnp->sip_tcp->state == LinphoneUpnpStateOk) {
485 port = lupnp->sip_tcp->external_port;
487 } else if(lupnp->sip_tls != NULL) {
488 if(lupnp->sip_tls->state == LinphoneUpnpStateOk) {
489 port = lupnp->sip_tls->external_port;
493 ms_mutex_unlock(&lupnp->mutex);
498 void linphone_upnp_refresh(UpnpContext * lupnp) {
499 upnp_igd_refresh(lupnp->upnp_igd_ctxt);
502 const char* linphone_upnp_context_get_external_ipaddress(UpnpContext *lupnp) {
503 const char* addr = NULL;
505 ms_mutex_lock(&lupnp->mutex);
506 addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
507 ms_mutex_unlock(&lupnp->mutex);
512 int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry) {
513 upnp_igd_port_mapping mapping;
514 char description[128];
517 if(lupnp->state != LinphoneUpnpStateOk) {
521 // Compute port binding state
522 if(port->state != LinphoneUpnpStateAdding) {
523 port->to_remove = FALSE;
524 switch(port->state) {
525 case LinphoneUpnpStateKo:
526 case LinphoneUpnpStateIdle: {
528 port->state = LinphoneUpnpStateAdding;
531 case LinphoneUpnpStateRemoving: {
541 // No retry if specified
542 if(port->retry != 0 && !retry) {
546 if(port->retry >= UPNP_ADD_MAX_RETRY) {
549 linphone_upnp_port_binding_set_device_id(port, upnp_igd_get_device_id(lupnp->upnp_igd_ctxt));
550 mapping.cookie = linphone_upnp_port_binding_retain(port);
551 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
553 mapping.local_port = port->local_port;
554 mapping.local_host = port->local_addr;
555 if(port->external_port == -1)
556 port->external_port = rand()%(0xffff - 1024) + 1024;
557 mapping.remote_port = port->external_port;
558 mapping.remote_host = "";
559 snprintf(description, 128, "%s %s at %s:%d",
561 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP": "UDP",
562 port->local_addr, port->local_port);
563 mapping.description = description;
564 mapping.protocol = port->protocol;
567 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to add port binding", port);
568 ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
571 port->state = LinphoneUpnpStateKo;
576 int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry) {
577 upnp_igd_port_mapping mapping;
580 if(lupnp->state != LinphoneUpnpStateOk) {
584 // Compute port binding state
585 if(port->state != LinphoneUpnpStateRemoving) {
586 port->to_add = FALSE;
587 switch(port->state) {
588 case LinphoneUpnpStateOk: {
590 port->state = LinphoneUpnpStateRemoving;
593 case LinphoneUpnpStateAdding: {
594 port->to_remove = TRUE;
603 // No retry if specified
604 if(port->retry != 0 && !retry) {
608 if(port->retry >= UPNP_REMOVE_MAX_RETRY) {
611 linphone_upnp_port_binding_set_device_id(port, upnp_igd_get_device_id(lupnp->upnp_igd_ctxt));
612 mapping.cookie = linphone_upnp_port_binding_retain(port);
613 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
615 mapping.remote_port = port->external_port;
616 mapping.remote_host = "";
617 mapping.protocol = port->protocol;
619 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to remove port binding", port);
620 ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
623 port->state = LinphoneUpnpStateKo;
629 * uPnP Core interfaces
632 int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool_t video) {
633 LinphoneCore *lc = call->core;
634 UpnpContext *lupnp = lc->upnp;
641 ms_mutex_lock(&lupnp->mutex);
643 // Don't handle when the call
644 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
650 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->audio->rtp,
651 UPNP_IGD_IP_PROTOCOL_UDP, (audio)? call->audio_port:0, UPNP_CALL_RETRY_DELAY);
653 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->audio->rtcp,
654 UPNP_IGD_IP_PROTOCOL_UDP, (audio)? call->audio_port+1:0, UPNP_CALL_RETRY_DELAY);
659 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->video->rtp,
660 UPNP_IGD_IP_PROTOCOL_UDP, (video)? call->video_port:0, UPNP_CALL_RETRY_DELAY);
662 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->video->rtcp,
663 UPNP_IGD_IP_PROTOCOL_UDP, (video)? call->video_port+1:0, UPNP_CALL_RETRY_DELAY);
666 ms_mutex_unlock(&lupnp->mutex);
669 * Update uPnP call state
671 linphone_upnp_call_process(call);
678 int linphone_core_update_upnp_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) {
679 bool_t audio = FALSE;
680 bool_t video = FALSE;
682 const SalStreamDescription *stream;
684 for (i = 0; i < md->n_total_streams; i++) {
685 stream = &md->streams[i];
686 if(stream->type == SalAudio) {
688 } else if(stream->type == SalVideo) {
693 return linphone_core_update_upnp_audio_video(call, audio, video);
696 int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
697 return linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
700 void linphone_core_update_upnp_state_in_call_stats(LinphoneCall *call) {
701 call->stats[LINPHONE_CALL_STATS_AUDIO].upnp_state = call->upnp_session->audio->state;
702 call->stats[LINPHONE_CALL_STATS_VIDEO].upnp_state = call->upnp_session->video->state;
705 void linphone_upnp_update_stream_state(UpnpStream *stream) {
706 if((stream->rtp == NULL || stream->rtp->state == LinphoneUpnpStateOk || stream->rtp->state == LinphoneUpnpStateIdle) &&
707 (stream->rtcp == NULL || stream->rtcp->state == LinphoneUpnpStateOk || stream->rtcp->state == LinphoneUpnpStateIdle)) {
708 stream->state = LinphoneUpnpStateOk;
709 } else if((stream->rtp != NULL &&
710 (stream->rtp->state == LinphoneUpnpStateAdding || stream->rtp->state == LinphoneUpnpStateRemoving)) ||
711 (stream->rtcp != NULL &&
712 (stream->rtcp->state == LinphoneUpnpStateAdding || stream->rtcp->state == LinphoneUpnpStateRemoving))) {
713 stream->state = LinphoneUpnpStatePending;
714 } else if((stream->rtp != NULL && stream->rtp->state == LinphoneUpnpStateKo) ||
715 (stream->rtcp != NULL && stream->rtcp->state == LinphoneUpnpStateKo)) {
716 stream->state = LinphoneUpnpStateKo;
718 ms_error("Invalid stream %p state", stream);
722 int linphone_upnp_call_process(LinphoneCall *call) {
723 LinphoneCore *lc = call->core;
724 UpnpContext *lupnp = lc->upnp;
726 LinphoneUpnpState oldState = 0, newState = 0;
732 ms_mutex_lock(&lupnp->mutex);
734 // Don't handle when the call
735 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
741 linphone_upnp_update_stream_state(call->upnp_session->audio);
746 linphone_upnp_update_stream_state(call->upnp_session->video);
751 linphone_core_update_upnp_state_in_call_stats(call);
754 * Update session state
756 oldState = call->upnp_session->state;
757 if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
758 call->upnp_session->video->state == LinphoneUpnpStateOk) {
759 call->upnp_session->state = LinphoneUpnpStateOk;
760 } else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
761 call->upnp_session->video->state == LinphoneUpnpStatePending) {
762 call->upnp_session->state = LinphoneUpnpStatePending;
763 } else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
764 call->upnp_session->video->state == LinphoneUpnpStateKo) {
765 call->upnp_session->state = LinphoneUpnpStateKo;
767 call->upnp_session->state = LinphoneUpnpStateIdle;
769 newState = call->upnp_session->state;
772 ms_mutex_unlock(&lupnp->mutex);
774 /* When change is done proceed update */
775 if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
776 (newState == LinphoneUpnpStateOk || newState == LinphoneUpnpStateKo)) {
777 if(call->upnp_session->state == LinphoneUpnpStateOk)
778 ms_message("uPnP IGD: uPnP for Call %p is ok", call);
780 ms_message("uPnP IGD: uPnP for Call %p is ko", call);
782 switch (call->state) {
783 case LinphoneCallUpdating:
784 linphone_core_start_update_call(lc, call);
786 case LinphoneCallUpdatedByRemote:
787 linphone_core_start_accept_call_update(lc, call);
789 case LinphoneCallOutgoingInit:
790 linphone_core_proceed_with_invite_if_ready(lc, call, NULL);
792 case LinphoneCallIdle:
793 linphone_core_notify_incoming_call(lc, call);
803 void linphone_core_upnp_refresh(UpnpContext *lupnp) {
804 MSList *global_list = NULL;
808 UpnpPortBinding *port_mapping, *port_mapping2;
810 ms_message("uPnP IGD: Refresh mappings");
812 if(lupnp->sip_udp != NULL) {
813 global_list = ms_list_append(global_list, lupnp->sip_udp);
815 if(lupnp->sip_tcp != NULL) {
816 global_list = ms_list_append(global_list, lupnp->sip_tcp);
818 if(lupnp->sip_tls != NULL) {
819 global_list = ms_list_append(global_list, lupnp->sip_tls);
822 list = lupnp->lc->calls;
823 while(list != NULL) {
824 call = (LinphoneCall *)list->data;
825 if(call->upnp_session != NULL) {
826 if(call->upnp_session->audio->rtp != NULL) {
827 global_list = ms_list_append(global_list, call->upnp_session->audio->rtp);
829 if(call->upnp_session->audio->rtcp != NULL) {
830 global_list = ms_list_append(global_list, call->upnp_session->audio->rtcp);
832 if(call->upnp_session->video->rtp != NULL) {
833 global_list = ms_list_append(global_list, call->upnp_session->video->rtp);
835 if(call->upnp_session->video->rtcp != NULL) {
836 global_list = ms_list_append(global_list, call->upnp_session->video->rtcp);
842 list = linphone_upnp_config_list_port_bindings(lupnp->lc->config, upnp_igd_get_device_id(lupnp->upnp_igd_ctxt));
843 for(item = list;item != NULL; item = item->next) {
844 port_mapping = (UpnpPortBinding *)item->data;
845 port_mapping2 = linphone_upnp_port_binding_equivalent_in_list(global_list, port_mapping);
846 if(port_mapping2 == NULL) {
847 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE);
848 } else if(port_mapping2->state == LinphoneUpnpStateIdle){
849 /* Force to remove */
850 port_mapping2->state = LinphoneUpnpStateOk;
853 ms_list_for_each(list, (void (*)(void*))linphone_upnp_port_binding_release);
854 list = ms_list_free(list);
857 // (Re)Add removed port bindings
859 while(list != NULL) {
860 port_mapping = (UpnpPortBinding *)list->data;
861 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE);
862 linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, TRUE);
865 global_list = ms_list_free(global_list);
868 void linphone_upnp_update_port_binding(UpnpContext *lupnp, UpnpPortBinding **port_mapping, upnp_igd_ip_protocol protocol, int port, int retry_delay) {
869 const char *local_addr, *external_addr;
870 time_t now = time(NULL);
872 if(*port_mapping != NULL) {
873 if(port != (*port_mapping)->local_port) {
874 linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
875 *port_mapping = NULL;
878 if(*port_mapping == NULL) {
879 *port_mapping = linphone_upnp_port_binding_new_or_collect(lupnp->pending_bindings, protocol, port, -1);
883 local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
884 external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
886 // Force binding update on local address change
887 if(local_addr != NULL) {
888 if(strncmp((*port_mapping)->local_addr, local_addr, sizeof((*port_mapping)->local_addr))) {
889 linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
890 strncpy((*port_mapping)->local_addr, local_addr, sizeof((*port_mapping)->local_addr));
893 if(external_addr != NULL) {
894 strncpy((*port_mapping)->external_addr, external_addr, sizeof((*port_mapping)->external_addr));
897 // Add (if not already done) the binding
898 if(now - (*port_mapping)->last_update >= retry_delay) {
899 (*port_mapping)->last_update = now;
900 linphone_upnp_context_send_add_port_binding(lupnp, *port_mapping, FALSE);
903 if(*port_mapping != NULL) {
904 linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
905 *port_mapping = NULL;
910 void linphone_upnp_update_config(UpnpContext* lupnp) {
913 UpnpPortBinding *port_mapping;
916 for(item = lupnp->adding_configs;item!=NULL;item=item->next) {
917 port_mapping = (UpnpPortBinding *)item->data;
918 snprintf(key, sizeof(key), "%s-%s-%d-%d",
919 port_mapping->device_id,
920 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
921 port_mapping->external_port,
922 port_mapping->local_port);
923 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, "uPnP");
924 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping);
926 ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
927 lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
930 for(item = lupnp->removing_configs;item!=NULL;item=item->next) {
931 port_mapping = (UpnpPortBinding *)item->data;
932 snprintf(key, sizeof(key), "%s-%s-%d-%d",
933 port_mapping->device_id,
934 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
935 port_mapping->external_port,
936 port_mapping->local_port);
937 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, NULL);
938 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping);
940 ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
941 lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
944 void linphone_upnp_update_proxy(UpnpContext* lupnp, bool_t force) {
945 LinphoneUpnpState ready_state;
947 time_t now = (force)? (lupnp->last_ready_check + UPNP_CORE_READY_CHECK) : time(NULL);
949 /* Refresh registers if we are ready */
950 if(now - lupnp->last_ready_check >= UPNP_CORE_READY_CHECK) {
951 lupnp->last_ready_check = now;
952 ready_state = (_linphone_upnp_context_is_ready_for_register(lupnp))? LinphoneUpnpStateOk: LinphoneUpnpStateKo;
953 if(ready_state != lupnp->last_ready_state) {
954 for(item=linphone_core_get_proxy_config_list(lupnp->lc);item!=NULL;item=item->next) {
955 LinphoneProxyConfig *cfg=(LinphoneProxyConfig*)item->data;
956 if (linphone_proxy_config_register_enabled(cfg)) {
957 if (ready_state != LinphoneUpnpStateOk) {
958 // Only reset ithe registration if we require that upnp should be ok
959 if(lupnp->lc->sip_conf.register_only_when_upnp_is_ok) {
960 linphone_proxy_config_set_state(cfg, LinphoneRegistrationNone, "Registration impossible (uPnP not ready)");
969 lupnp->last_ready_state = ready_state;
974 bool_t linphone_core_upnp_hook(void *data) {
975 LCSipTransports transport;
976 UpnpContext *lupnp = (UpnpContext *)data;
978 ms_mutex_lock(&lupnp->mutex);
981 if(lupnp->state == LinphoneUpnpStateOk) {
982 linphone_core_get_sip_transports(lupnp->lc, &transport);
983 linphone_upnp_update_port_binding(lupnp, &lupnp->sip_udp, UPNP_IGD_IP_PROTOCOL_UDP, transport.udp_port, UPNP_CORE_RETRY_DELAY);
984 linphone_upnp_update_port_binding(lupnp, &lupnp->sip_tcp, UPNP_IGD_IP_PROTOCOL_TCP, transport.tcp_port, UPNP_CORE_RETRY_DELAY);
985 linphone_upnp_update_port_binding(lupnp, &lupnp->sip_tls, UPNP_IGD_IP_PROTOCOL_TCP, transport.tls_port, UPNP_CORE_RETRY_DELAY);
988 linphone_upnp_update_proxy(lupnp, FALSE);
989 linphone_upnp_update_config(lupnp);
991 ms_mutex_unlock(&lupnp->mutex);
995 int linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
997 SalStreamDescription *stream;
998 UpnpStream *upnpStream;
1000 for (i = 0; i < desc->n_active_streams; i++) {
1001 stream = &desc->streams[i];
1003 if(stream->type == SalAudio) {
1004 upnpStream = session->audio;
1005 } else if(stream->type == SalVideo) {
1006 upnpStream = session->video;
1008 if(upnpStream != NULL) {
1009 if(upnpStream->rtp != NULL && upnpStream->rtp->state == LinphoneUpnpStateOk) {
1010 strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
1011 stream->rtp_port = upnpStream->rtp->external_port;
1013 if(upnpStream->rtcp != NULL && upnpStream->rtcp->state == LinphoneUpnpStateOk) {
1014 strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
1015 stream->rtcp_port = upnpStream->rtcp->external_port;
1027 UpnpPortBinding *linphone_upnp_port_binding_new() {
1028 UpnpPortBinding *port = NULL;
1029 port = ms_new0(UpnpPortBinding,1);
1030 ms_mutex_init(&port->mutex, NULL);
1031 port->state = LinphoneUpnpStateIdle;
1032 port->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
1033 port->device_id = NULL;
1034 port->local_addr[0] = '\0';
1035 port->local_port = -1;
1036 port->external_addr[0] = '\0';
1037 port->external_port = -1;
1038 port->to_remove = FALSE;
1039 port->to_add = FALSE;
1041 port->last_update = 0;
1045 UpnpPortBinding *linphone_upnp_port_binding_new_with_parameters(upnp_igd_ip_protocol protocol, int local_port, int external_port) {
1046 UpnpPortBinding *port_binding = linphone_upnp_port_binding_new();
1047 port_binding->protocol = protocol;
1048 port_binding->local_port = local_port;
1049 port_binding->external_port = external_port;
1050 return port_binding;
1053 UpnpPortBinding *linphone_upnp_port_binding_new_or_collect(MSList *list, upnp_igd_ip_protocol protocol, int local_port, int external_port) {
1054 UpnpPortBinding *tmp_binding;
1055 UpnpPortBinding *end_binding;
1056 end_binding = linphone_upnp_port_binding_new_with_parameters(protocol, local_port, external_port);
1057 tmp_binding = linphone_upnp_port_binding_equivalent_in_list(list, end_binding);
1058 if(tmp_binding != NULL) {
1059 linphone_upnp_port_binding_release(end_binding);
1060 end_binding = tmp_binding;
1065 UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port) {
1066 UpnpPortBinding *new_port = NULL;
1067 new_port = ms_new0(UpnpPortBinding,1);
1068 memcpy(new_port, port, sizeof(UpnpPortBinding));
1069 new_port->device_id = NULL;
1070 linphone_upnp_port_binding_set_device_id(new_port, port->device_id);
1071 ms_mutex_init(&new_port->mutex, NULL);
1076 void linphone_upnp_port_binding_set_device_id(UpnpPortBinding *port, const char *device_id) {
1077 char *formated_device_id = linphone_upnp_format_device_id(device_id);
1078 if(formated_device_id != NULL && port->device_id != NULL) {
1079 if(strcmp(formated_device_id, port->device_id) == 0) {
1080 ms_free(formated_device_id);
1084 if(port->device_id != NULL) {
1085 ms_free(port->device_id);
1087 port->device_id = formated_device_id;
1090 void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port) {
1091 if(strlen(port->local_addr)) {
1092 ortp_log(level, "uPnP IGD: %s %s|%d->%s:%d (retry %d)", msg,
1093 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
1094 port->external_port,
1099 ortp_log(level, "uPnP IGD: %s %s|%d->%d (retry %d)", msg,
1100 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
1101 port->external_port,
1107 bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
1108 return port1->protocol == port2->protocol &&
1109 port1->local_port == port2->local_port &&
1110 (port1->external_port == -1 || port2->external_port == -1 || port1->external_port == port2->external_port);
1113 UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port) {
1114 UpnpPortBinding *port_mapping;
1115 while(list != NULL) {
1116 port_mapping = (UpnpPortBinding *)list->data;
1117 if(linphone_upnp_port_binding_equal(port, port_mapping)) {
1118 return port_mapping;
1126 UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port) {
1127 ms_mutex_lock(&port->mutex);
1129 ms_mutex_unlock(&port->mutex);
1133 void linphone_upnp_port_binding_release(UpnpPortBinding *port) {
1134 ms_mutex_lock(&port->mutex);
1135 if(--port->ref == 0) {
1136 if(port->device_id != NULL) {
1137 ms_free(port->device_id);
1139 ms_mutex_unlock(&port->mutex);
1140 ms_mutex_destroy(&port->mutex);
1144 ms_mutex_unlock(&port->mutex);
1152 UpnpStream* linphone_upnp_stream_new() {
1153 UpnpStream *stream = ms_new0(UpnpStream,1);
1154 stream->state = LinphoneUpnpStateIdle;
1156 stream->rtcp = NULL;
1160 void linphone_upnp_stream_destroy(UpnpStream* stream) {
1161 if(stream->rtp != NULL) {
1162 linphone_upnp_port_binding_release(stream->rtp);
1165 if(stream->rtcp != NULL) {
1166 linphone_upnp_port_binding_release(stream->rtcp);
1167 stream->rtcp = NULL;
1177 UpnpSession* linphone_upnp_session_new(LinphoneCall* call) {
1178 UpnpSession *session = ms_new0(UpnpSession,1);
1179 session->call = call;
1180 session->state = LinphoneUpnpStateIdle;
1181 session->audio = linphone_upnp_stream_new();
1182 session->video = linphone_upnp_stream_new();
1186 void linphone_upnp_session_destroy(UpnpSession *session) {
1187 LinphoneCore *lc = session->call->core;
1189 if(lc->upnp != NULL) {
1190 /* Remove bindings */
1191 if(session->audio->rtp != NULL) {
1192 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtp, TRUE);
1194 if(session->audio->rtcp != NULL) {
1195 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtcp, TRUE);
1197 if(session->video->rtp != NULL) {
1198 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtp, TRUE);
1200 if(session->video->rtcp != NULL) {
1201 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtcp, TRUE);
1205 session->call->stats[LINPHONE_CALL_STATS_AUDIO].upnp_state = LinphoneUpnpStateKo;
1206 session->call->stats[LINPHONE_CALL_STATS_VIDEO].upnp_state = LinphoneUpnpStateKo;
1208 linphone_upnp_stream_destroy(session->audio);
1209 linphone_upnp_stream_destroy(session->video);
1213 LinphoneUpnpState linphone_upnp_session_get_state(UpnpSession *session) {
1214 return session->state;
1222 struct linphone_upnp_config_list_port_bindings_struct {
1223 struct _LpConfig *lpc;
1225 const char *device_id;
1228 static void linphone_upnp_config_list_port_bindings_cb(const char *entry, struct linphone_upnp_config_list_port_bindings_struct *cookie) {
1229 char device_id[UPNP_UUID_LEN + 1];
1230 char protocol_str[4]; // TCP or UDP
1231 upnp_igd_ip_protocol protocol;
1235 bool_t valid = TRUE;
1236 UpnpPortBinding *port;
1238 ret = sscanf(entry, "%"UPNP_UUID_LEN_STR"[^-]-%3s-%i-%i", device_id, protocol_str, &external_port, &local_port);
1240 // Handle only wanted device bindings
1241 if(device_id != NULL && strcmp(cookie->device_id, device_id) != 0) {
1244 if(linphone_upnp_strncmpi(protocol_str, "TCP", 3) == 0) {
1245 protocol = UPNP_IGD_IP_PROTOCOL_TCP;
1246 } else if(linphone_upnp_strncmpi(protocol_str, "UDP", 3) == 0) {
1247 protocol = UPNP_IGD_IP_PROTOCOL_UDP;
1252 port = linphone_upnp_port_binding_new();
1253 linphone_upnp_port_binding_set_device_id(port, device_id);
1254 port->state = LinphoneUpnpStateOk;
1255 port->protocol = protocol;
1256 port->external_port = external_port;
1257 port->local_port = local_port;
1258 cookie->retList = ms_list_append(cookie->retList, port);
1264 ms_warning("uPnP configuration invalid line: %s", entry);
1268 MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc, const char *device_id) {
1269 char *formated_device_id = linphone_upnp_format_device_id(device_id);
1270 struct linphone_upnp_config_list_port_bindings_struct cookie = {lpc, NULL, formated_device_id};
1271 lp_config_for_each_entry(lpc, UPNP_SECTION_NAME, (void(*)(const char *, void*))linphone_upnp_config_list_port_bindings_cb, &cookie);
1272 ms_free(formated_device_id);
1273 return cookie.retList;
1276 void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1278 UpnpPortBinding *list_port;
1280 if(port->device_id == NULL) {
1281 ms_error("Can't remove port binding without device_id");
1285 list = lupnp->removing_configs;
1286 while(list != NULL) {
1287 list_port = (UpnpPortBinding *)list->data;
1288 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1289 lupnp->removing_configs = ms_list_remove(lupnp->removing_configs, list_port);
1290 linphone_upnp_port_binding_release(list_port);
1293 list = ms_list_next(list);
1296 list = lupnp->adding_configs;
1297 while(list != NULL) {
1298 list_port = (UpnpPortBinding *)list->data;
1299 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1302 list = ms_list_next(list);
1305 list_port = linphone_upnp_port_binding_copy(port);
1306 lupnp->adding_configs = ms_list_append(lupnp->adding_configs, list_port);
1309 void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1311 UpnpPortBinding *list_port;
1313 if(port->device_id == NULL) {
1314 ms_error("Can't remove port binding without device_id");
1318 list = lupnp->adding_configs;
1319 while(list != NULL) {
1320 list_port = (UpnpPortBinding *)list->data;
1321 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1322 lupnp->adding_configs = ms_list_remove(lupnp->adding_configs, list_port);
1323 linphone_upnp_port_binding_release(list_port);
1326 list = ms_list_next(list);
1329 list = lupnp->removing_configs;
1330 while(list != NULL) {
1331 list_port = (UpnpPortBinding *)list->data;
1332 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1335 list = ms_list_next(list);
1338 list_port = linphone_upnp_port_binding_copy(port);
1339 lupnp->removing_configs = ms_list_append(lupnp->removing_configs, list_port);