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 32
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) {
121 char1 = toupper(*str1);
122 char2 = toupper(*str2);
123 if(char1 == '\0' || char2 == '\0' || char1 != char2) {
124 return char1 - char2;
132 static int linphone_upnp_str_min(const char *str1, const char *str2) {
133 int len1 = strlen(str1);
134 int len2 = strlen(str2);
141 char * linphone_upnp_format_device_id(const char *device_id) {
146 if(device_id == NULL) {
149 ret = ms_new(char, UPNP_UUID_LEN + 1);
151 if(linphone_upnp_strncmpi(device_id, "uuid:", linphone_upnp_str_min(device_id, "uuid:")) == 0) {
152 device_id += strlen("uuid:");
154 while(*device_id != '\0' && tmp - ret < UPNP_UUID_LEN) {
157 if(tchar >= '0' && tchar <= '9')
159 if(!copy && tchar >= 'A' && tchar <= 'Z')
161 if(!copy && tchar >= 'a' && tchar <= 'z')
177 /* Convert uPnP IGD logs to ortp logs */
178 void linphone_upnp_igd_print(void *cookie, upnp_igd_print_level level, const char *fmt, va_list list) {
179 int ortp_level = ORTP_DEBUG;
181 case UPNP_IGD_MESSAGE:
182 ortp_level = ORTP_MESSAGE;
184 case UPNP_IGD_WARNING:
185 ortp_level = ORTP_DEBUG; // Too verbose otherwise
188 ortp_level = ORTP_DEBUG; // Too verbose otherwise
193 ortp_logv(ortp_level, fmt, list);
196 void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) {
197 UpnpContext *lupnp = (UpnpContext *)cookie;
198 upnp_igd_port_mapping *mapping = NULL;
199 UpnpPortBinding *port_mapping = NULL;
200 const char *ip_address = NULL;
201 const char *connection_status = NULL;
202 bool_t nat_enabled = FALSE;
203 LinphoneUpnpState old_state;
205 if(lupnp == NULL || lupnp->upnp_igd_ctxt == NULL) {
206 ms_error("uPnP IGD: Invalid context in callback");
210 ms_mutex_lock(&lupnp->mutex);
211 old_state = lupnp->state;
214 case UPNP_IGD_DEVICE_ADDED:
215 case UPNP_IGD_DEVICE_REMOVED:
216 case UPNP_IGD_EXTERNAL_IPADDRESS_CHANGED:
217 case UPNP_IGD_NAT_ENABLED_CHANGED:
218 case UPNP_IGD_CONNECTION_STATUS_CHANGED:
219 ip_address = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
220 connection_status = upnp_igd_get_connection_status(lupnp->upnp_igd_ctxt);
221 nat_enabled = upnp_igd_get_nat_enabled(lupnp->upnp_igd_ctxt);
223 if(ip_address == NULL || connection_status == NULL) {
224 ms_message("uPnP IGD: Pending");
225 lupnp->state = LinphoneUpnpStatePending;
226 } else if(strcasecmp(connection_status, "Connected") || !nat_enabled) {
227 ms_message("uPnP IGD: Not Available");
228 lupnp->state = LinphoneUpnpStateNotAvailable;
230 ms_message("uPnP IGD: Connected");
231 lupnp->state = LinphoneUpnpStateOk;
232 if(old_state != LinphoneUpnpStateOk) {
233 linphone_core_upnp_refresh(lupnp);
239 case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS:
240 mapping = (upnp_igd_port_mapping *) arg;
241 port_mapping = (UpnpPortBinding*) mapping->cookie;
242 port_mapping->external_port = mapping->remote_port;
243 port_mapping->state = LinphoneUpnpStateOk;
244 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Added port binding", port_mapping);
245 linphone_upnp_config_add_port_binding(lupnp, port_mapping);
249 case UPNP_IGD_PORT_MAPPING_ADD_FAILURE:
250 mapping = (upnp_igd_port_mapping *) arg;
251 port_mapping = (UpnpPortBinding*) mapping->cookie;
252 port_mapping->external_port = -1; //Force random external port
253 if(linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, TRUE) != 0) {
254 linphone_upnp_port_binding_log(ORTP_ERROR, "Can't add port binding", port_mapping);
259 case UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS:
260 mapping = (upnp_igd_port_mapping *) arg;
261 port_mapping = (UpnpPortBinding*) mapping->cookie;
262 port_mapping->state = LinphoneUpnpStateIdle;
263 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Removed port binding", port_mapping);
264 linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
268 case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE:
269 mapping = (upnp_igd_port_mapping *) arg;
270 port_mapping = (UpnpPortBinding*) mapping->cookie;
271 if(linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE) != 0) {
272 linphone_upnp_port_binding_log(ORTP_ERROR, "Can't remove port binding", port_mapping);
273 linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
282 if(port_mapping != NULL) {
284 * Execute delayed actions
286 if(port_mapping->to_remove) {
287 if(port_mapping->state == LinphoneUpnpStateOk) {
288 port_mapping->to_remove = FALSE;
289 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, FALSE);
290 } else if(port_mapping->state == LinphoneUpnpStateKo) {
291 port_mapping->to_remove = FALSE;
294 if(port_mapping->to_add) {
295 if(port_mapping->state == LinphoneUpnpStateIdle || port_mapping->state == LinphoneUpnpStateKo) {
296 port_mapping->to_add = FALSE;
297 linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, FALSE);
301 lupnp->pending_bindings = ms_list_remove(lupnp->pending_bindings, port_mapping);
302 linphone_upnp_port_binding_release(port_mapping);
306 * If there is no pending binding emit a signal
308 if(lupnp->pending_bindings == NULL) {
309 ms_cond_signal(&lupnp->empty_cond);
311 ms_mutex_unlock(&lupnp->mutex);
319 UpnpContext* linphone_upnp_context_new(LinphoneCore *lc) {
320 UpnpContext *lupnp = (UpnpContext *)ms_new0(UpnpContext,1);
322 ms_mutex_init(&lupnp->mutex, NULL);
323 ms_cond_init(&lupnp->empty_cond, NULL);
325 lupnp->last_ready_check = 0;
326 lupnp->last_ready_state = LinphoneUpnpStateIdle;
329 lupnp->pending_bindings = NULL;
330 lupnp->adding_configs = NULL;
331 lupnp->removing_configs = NULL;
332 lupnp->state = LinphoneUpnpStateIdle;
333 ms_message("uPnP IGD: New %p for core %p", lupnp, lc);
336 lupnp->sip_udp = NULL;
337 lupnp->sip_tcp = NULL;
338 lupnp->sip_tls = NULL;
340 linphone_core_add_iterate_hook(lc, linphone_core_upnp_hook, lupnp);
342 lupnp->upnp_igd_ctxt = NULL;
343 lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, lupnp);
344 if(lupnp->upnp_igd_ctxt == NULL) {
345 lupnp->state = LinphoneUpnpStateKo;
346 ms_error("Can't create uPnP IGD context");
350 lupnp->state = LinphoneUpnpStatePending;
351 upnp_igd_start(lupnp->upnp_igd_ctxt);
356 void linphone_upnp_context_destroy(UpnpContext *lupnp) {
357 linphone_core_remove_iterate_hook(lupnp->lc, linphone_core_upnp_hook, lupnp);
359 ms_mutex_lock(&lupnp->mutex);
361 if(lupnp->lc->network_reachable) {
362 /* Send port binding removes */
363 if(lupnp->sip_udp != NULL) {
364 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_udp, TRUE);
366 if(lupnp->sip_tcp != NULL) {
367 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tcp, TRUE);
369 if(lupnp->sip_tls != NULL) {
370 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tls, TRUE);
374 /* Wait all pending bindings are done */
375 if(lupnp->pending_bindings != NULL) {
376 ms_message("uPnP IGD: Wait all pending port bindings ...");
377 ms_cond_wait(&lupnp->empty_cond, &lupnp->mutex);
379 ms_mutex_unlock(&lupnp->mutex);
381 if(lupnp->upnp_igd_ctxt != NULL) {
382 upnp_igd_destroy(lupnp->upnp_igd_ctxt);
383 lupnp->upnp_igd_ctxt = NULL;
386 /* No more multi threading here */
388 /* Run one more time configuration update and proxy */
389 linphone_upnp_update_config(lupnp);
390 linphone_upnp_update_proxy(lupnp, TRUE);
392 /* Release port bindings */
393 if(lupnp->sip_udp != NULL) {
394 linphone_upnp_port_binding_release(lupnp->sip_udp);
395 lupnp->sip_udp = NULL;
397 if(lupnp->sip_tcp != NULL) {
398 linphone_upnp_port_binding_release(lupnp->sip_tcp);
399 lupnp->sip_tcp = NULL;
401 if(lupnp->sip_tls != NULL) {
402 linphone_upnp_port_binding_release(lupnp->sip_tls);
403 lupnp->sip_tcp = NULL;
407 ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
408 lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
409 ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
410 lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
411 ms_list_for_each(lupnp->pending_bindings,(void (*)(void*))linphone_upnp_port_binding_release);
412 lupnp->pending_bindings = ms_list_free(lupnp->pending_bindings);
414 ms_mutex_destroy(&lupnp->mutex);
415 ms_cond_destroy(&lupnp->empty_cond);
417 ms_message("uPnP IGD: destroy %p", lupnp);
421 LinphoneUpnpState linphone_upnp_context_get_state(UpnpContext *lupnp) {
422 LinphoneUpnpState state = LinphoneUpnpStateKo;
424 ms_mutex_lock(&lupnp->mutex);
425 state = lupnp->state;
426 ms_mutex_unlock(&lupnp->mutex);
431 bool_t _linphone_upnp_context_is_ready_for_register(UpnpContext *lupnp) {
434 // 1 Check global uPnP state
435 ready = (lupnp->state == LinphoneUpnpStateOk);
437 // 2 Check external ip address
439 if (upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt) == NULL) {
444 // 3 Check sip ports bindings
446 if(lupnp->sip_udp != NULL) {
447 if(lupnp->sip_udp->state != LinphoneUpnpStateOk) {
450 } else if(lupnp->sip_tcp != NULL) {
451 if(lupnp->sip_tcp->state != LinphoneUpnpStateOk) {
454 } else if(lupnp->sip_tls != NULL) {
455 if(lupnp->sip_tls->state != LinphoneUpnpStateOk) {
466 bool_t linphone_upnp_context_is_ready_for_register(UpnpContext *lupnp) {
467 bool_t ready = FALSE;
469 ms_mutex_lock(&lupnp->mutex);
470 ready = _linphone_upnp_context_is_ready_for_register(lupnp);
471 ms_mutex_unlock(&lupnp->mutex);
476 int linphone_upnp_context_get_external_port(UpnpContext *lupnp) {
479 ms_mutex_lock(&lupnp->mutex);
481 if(lupnp->sip_udp != NULL) {
482 if(lupnp->sip_udp->state == LinphoneUpnpStateOk) {
483 port = lupnp->sip_udp->external_port;
485 } else if(lupnp->sip_tcp != NULL) {
486 if(lupnp->sip_tcp->state == LinphoneUpnpStateOk) {
487 port = lupnp->sip_tcp->external_port;
489 } else if(lupnp->sip_tls != NULL) {
490 if(lupnp->sip_tls->state == LinphoneUpnpStateOk) {
491 port = lupnp->sip_tls->external_port;
495 ms_mutex_unlock(&lupnp->mutex);
500 void linphone_upnp_refresh(UpnpContext * lupnp) {
501 upnp_igd_refresh(lupnp->upnp_igd_ctxt);
504 const char* linphone_upnp_context_get_external_ipaddress(UpnpContext *lupnp) {
505 const char* addr = NULL;
507 ms_mutex_lock(&lupnp->mutex);
508 addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
509 ms_mutex_unlock(&lupnp->mutex);
514 int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry) {
515 upnp_igd_port_mapping mapping;
516 char description[128];
519 if(lupnp->state != LinphoneUpnpStateOk) {
523 // Compute port binding state
524 if(port->state != LinphoneUpnpStateAdding) {
525 port->to_remove = FALSE;
526 switch(port->state) {
527 case LinphoneUpnpStateKo:
528 case LinphoneUpnpStateIdle: {
530 port->state = LinphoneUpnpStateAdding;
533 case LinphoneUpnpStateRemoving: {
543 // No retry if specified
544 if(port->retry != 0 && !retry) {
548 if(port->retry >= UPNP_ADD_MAX_RETRY) {
551 linphone_upnp_port_binding_set_device_id(port, upnp_igd_get_device_id(lupnp->upnp_igd_ctxt));
552 mapping.cookie = linphone_upnp_port_binding_retain(port);
553 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
555 mapping.local_port = port->local_port;
556 mapping.local_host = port->local_addr;
557 if(port->external_port == -1)
558 mapping.remote_port = rand()%(0xffff - 1024) + 1024;
560 mapping.remote_port = port->external_port;
561 mapping.remote_host = "";
562 snprintf(description, 128, "%s %s at %s:%d",
564 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP": "UDP",
565 port->local_addr, port->local_port);
566 mapping.description = description;
567 mapping.protocol = port->protocol;
570 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to add port binding", port);
571 ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
574 port->state = LinphoneUpnpStateKo;
579 int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry) {
580 upnp_igd_port_mapping mapping;
583 if(lupnp->state != LinphoneUpnpStateOk) {
587 // Compute port binding state
588 if(port->state != LinphoneUpnpStateRemoving) {
589 port->to_add = FALSE;
590 switch(port->state) {
591 case LinphoneUpnpStateOk: {
593 port->state = LinphoneUpnpStateRemoving;
596 case LinphoneUpnpStateAdding: {
597 port->to_remove = TRUE;
606 // No retry if specified
607 if(port->retry != 0 && !retry) {
611 if(port->retry >= UPNP_REMOVE_MAX_RETRY) {
614 linphone_upnp_port_binding_set_device_id(port, upnp_igd_get_device_id(lupnp->upnp_igd_ctxt));
615 mapping.cookie = linphone_upnp_port_binding_retain(port);
616 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
618 mapping.remote_port = port->external_port;
619 mapping.remote_host = "";
620 mapping.protocol = port->protocol;
622 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to remove port binding", port);
623 ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
626 port->state = LinphoneUpnpStateKo;
632 * uPnP Core interfaces
635 int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool_t video) {
636 LinphoneCore *lc = call->core;
637 UpnpContext *lupnp = lc->upnp;
644 ms_mutex_lock(&lupnp->mutex);
646 // Don't handle when the call
647 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
653 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->audio->rtp,
654 UPNP_IGD_IP_PROTOCOL_UDP, (audio)? call->audio_port:0, UPNP_CALL_RETRY_DELAY);
656 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->audio->rtcp,
657 UPNP_IGD_IP_PROTOCOL_UDP, (audio)? call->audio_port+1:0, UPNP_CALL_RETRY_DELAY);
662 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->video->rtp,
663 UPNP_IGD_IP_PROTOCOL_UDP, (video)? call->video_port:0, UPNP_CALL_RETRY_DELAY);
665 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->video->rtcp,
666 UPNP_IGD_IP_PROTOCOL_UDP, (video)? call->video_port+1:0, UPNP_CALL_RETRY_DELAY);
669 ms_mutex_unlock(&lupnp->mutex);
672 * Update uPnP call state
674 linphone_upnp_call_process(call);
681 int linphone_core_update_upnp_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) {
682 bool_t audio = FALSE;
683 bool_t video = FALSE;
685 const SalStreamDescription *stream;
687 for (i = 0; i < md->n_total_streams; i++) {
688 stream = &md->streams[i];
689 if(stream->type == SalAudio) {
691 } else if(stream->type == SalVideo) {
696 return linphone_core_update_upnp_audio_video(call, audio, video);
699 int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
700 return linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
703 void linphone_core_update_upnp_state_in_call_stats(LinphoneCall *call) {
704 call->stats[LINPHONE_CALL_STATS_AUDIO].upnp_state = call->upnp_session->audio->state;
705 call->stats[LINPHONE_CALL_STATS_VIDEO].upnp_state = call->upnp_session->video->state;
708 void linphone_upnp_update_stream_state(UpnpStream *stream) {
709 if((stream->rtp == NULL || stream->rtp->state == LinphoneUpnpStateOk || stream->rtp->state == LinphoneUpnpStateIdle) &&
710 (stream->rtcp == NULL || stream->rtcp->state == LinphoneUpnpStateOk || stream->rtcp->state == LinphoneUpnpStateIdle)) {
711 stream->state = LinphoneUpnpStateOk;
712 } else if((stream->rtp != NULL &&
713 (stream->rtp->state == LinphoneUpnpStateAdding || stream->rtp->state == LinphoneUpnpStateRemoving)) ||
714 (stream->rtcp != NULL &&
715 (stream->rtcp->state == LinphoneUpnpStateAdding || stream->rtcp->state == LinphoneUpnpStateRemoving))) {
716 stream->state = LinphoneUpnpStatePending;
717 } else if((stream->rtp != NULL && stream->rtp->state == LinphoneUpnpStateKo) ||
718 (stream->rtcp != NULL && stream->rtcp->state == LinphoneUpnpStateKo)) {
719 stream->state = LinphoneUpnpStateKo;
721 ms_error("Invalid stream %p state", stream);
725 int linphone_upnp_call_process(LinphoneCall *call) {
726 LinphoneCore *lc = call->core;
727 UpnpContext *lupnp = lc->upnp;
729 LinphoneUpnpState oldState = 0, newState = 0;
735 ms_mutex_lock(&lupnp->mutex);
737 // Don't handle when the call
738 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
744 linphone_upnp_update_stream_state(call->upnp_session->audio);
749 linphone_upnp_update_stream_state(call->upnp_session->video);
754 linphone_core_update_upnp_state_in_call_stats(call);
757 * Update session state
759 oldState = call->upnp_session->state;
760 if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
761 call->upnp_session->video->state == LinphoneUpnpStateOk) {
762 call->upnp_session->state = LinphoneUpnpStateOk;
763 } else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
764 call->upnp_session->video->state == LinphoneUpnpStatePending) {
765 call->upnp_session->state = LinphoneUpnpStatePending;
766 } else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
767 call->upnp_session->video->state == LinphoneUpnpStateKo) {
768 call->upnp_session->state = LinphoneUpnpStateKo;
770 call->upnp_session->state = LinphoneUpnpStateIdle;
772 newState = call->upnp_session->state;
775 ms_mutex_unlock(&lupnp->mutex);
777 /* When change is done proceed update */
778 if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
779 (newState == LinphoneUpnpStateOk || newState == LinphoneUpnpStateKo)) {
780 if(call->upnp_session->state == LinphoneUpnpStateOk)
781 ms_message("uPnP IGD: uPnP for Call %p is ok", call);
783 ms_message("uPnP IGD: uPnP for Call %p is ko", call);
785 switch (call->state) {
786 case LinphoneCallUpdating:
787 linphone_core_start_update_call(lc, call);
789 case LinphoneCallUpdatedByRemote:
790 linphone_core_start_accept_call_update(lc, call);
792 case LinphoneCallOutgoingInit:
793 linphone_core_proceed_with_invite_if_ready(lc, call, NULL);
795 case LinphoneCallIdle:
796 linphone_core_notify_incoming_call(lc, call);
806 void linphone_core_upnp_refresh(UpnpContext *lupnp) {
807 MSList *global_list = NULL;
811 UpnpPortBinding *port_mapping, *port_mapping2;
813 ms_message("uPnP IGD: Refresh mappings");
815 if(lupnp->sip_udp != NULL) {
816 global_list = ms_list_append(global_list, lupnp->sip_udp);
818 if(lupnp->sip_tcp != NULL) {
819 global_list = ms_list_append(global_list, lupnp->sip_tcp);
821 if(lupnp->sip_tls != NULL) {
822 global_list = ms_list_append(global_list, lupnp->sip_tls);
825 list = lupnp->lc->calls;
826 while(list != NULL) {
827 call = (LinphoneCall *)list->data;
828 if(call->upnp_session != NULL) {
829 if(call->upnp_session->audio->rtp != NULL) {
830 global_list = ms_list_append(global_list, call->upnp_session->audio->rtp);
832 if(call->upnp_session->audio->rtcp != NULL) {
833 global_list = ms_list_append(global_list, call->upnp_session->audio->rtcp);
835 if(call->upnp_session->video->rtp != NULL) {
836 global_list = ms_list_append(global_list, call->upnp_session->video->rtp);
838 if(call->upnp_session->video->rtcp != NULL) {
839 global_list = ms_list_append(global_list, call->upnp_session->video->rtcp);
845 list = linphone_upnp_config_list_port_bindings(lupnp->lc->config, upnp_igd_get_device_id(lupnp->upnp_igd_ctxt));
846 for(item = list;item != NULL; item = item->next) {
847 port_mapping = (UpnpPortBinding *)item->data;
848 port_mapping2 = linphone_upnp_port_binding_equivalent_in_list(global_list, port_mapping);
849 if(port_mapping2 == NULL) {
850 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE);
851 } else if(port_mapping2->state == LinphoneUpnpStateIdle){
852 /* Force to remove */
853 port_mapping2->state = LinphoneUpnpStateOk;
856 ms_list_for_each(list, (void (*)(void*))linphone_upnp_port_binding_release);
857 list = ms_list_free(list);
860 // (Re)Add removed port bindings
862 while(list != NULL) {
863 port_mapping = (UpnpPortBinding *)list->data;
864 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE);
865 linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, TRUE);
868 global_list = ms_list_free(global_list);
871 void linphone_upnp_update_port_binding(UpnpContext *lupnp, UpnpPortBinding **port_mapping, upnp_igd_ip_protocol protocol, int port, int retry_delay) {
872 const char *local_addr, *external_addr;
873 time_t now = time(NULL);
875 if(*port_mapping != NULL) {
876 if(port != (*port_mapping)->local_port) {
877 linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
878 *port_mapping = NULL;
881 if(*port_mapping == NULL) {
882 *port_mapping = linphone_upnp_port_binding_new_or_collect(lupnp->pending_bindings, protocol, port, port);
886 local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
887 external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
889 // Force binding update on local address change
890 if(local_addr != NULL) {
891 if(strncmp((*port_mapping)->local_addr, local_addr, sizeof((*port_mapping)->local_addr))) {
892 linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
893 strncpy((*port_mapping)->local_addr, local_addr, sizeof((*port_mapping)->local_addr));
896 if(external_addr != NULL) {
897 strncpy((*port_mapping)->external_addr, external_addr, sizeof((*port_mapping)->external_addr));
900 // Add (if not already done) the binding
901 if(now - (*port_mapping)->last_update >= retry_delay) {
902 (*port_mapping)->last_update = now;
903 linphone_upnp_context_send_add_port_binding(lupnp, *port_mapping, FALSE);
906 if(*port_mapping != NULL) {
907 linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
908 *port_mapping = NULL;
913 void linphone_upnp_update_config(UpnpContext* lupnp) {
916 UpnpPortBinding *port_mapping;
919 for(item = lupnp->adding_configs;item!=NULL;item=item->next) {
920 port_mapping = (UpnpPortBinding *)item->data;
921 snprintf(key, sizeof(key), "%s-%s-%d-%d",
922 port_mapping->device_id,
923 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
924 port_mapping->external_port,
925 port_mapping->local_port);
926 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, "uPnP");
927 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping);
929 ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
930 lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
933 for(item = lupnp->removing_configs;item!=NULL;item=item->next) {
934 port_mapping = (UpnpPortBinding *)item->data;
935 snprintf(key, sizeof(key), "%s-%s-%d-%d",
936 port_mapping->device_id,
937 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
938 port_mapping->external_port,
939 port_mapping->local_port);
940 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, NULL);
941 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping);
943 ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
944 lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
947 void linphone_upnp_update_proxy(UpnpContext* lupnp, bool_t force) {
948 LinphoneUpnpState ready_state;
950 time_t now = (force)? (lupnp->last_ready_check + UPNP_CORE_READY_CHECK) : time(NULL);
952 /* Refresh registers if we are ready */
953 if(now - lupnp->last_ready_check >= UPNP_CORE_READY_CHECK) {
954 lupnp->last_ready_check = now;
955 ready_state = (_linphone_upnp_context_is_ready_for_register(lupnp))? LinphoneUpnpStateOk: LinphoneUpnpStateKo;
956 if(ready_state != lupnp->last_ready_state) {
957 for(item=linphone_core_get_proxy_config_list(lupnp->lc);item!=NULL;item=item->next) {
958 LinphoneProxyConfig *cfg=(LinphoneProxyConfig*)item->data;
959 if (linphone_proxy_config_register_enabled(cfg)) {
960 if (ready_state != LinphoneUpnpStateOk) {
961 // Only reset ithe registration if we require that upnp should be ok
962 if(lupnp->lc->sip_conf.register_only_when_upnp_is_ok) {
963 linphone_proxy_config_set_state(cfg, LinphoneRegistrationNone, "Registration impossible (uPnP not ready)");
972 lupnp->last_ready_state = ready_state;
977 bool_t linphone_core_upnp_hook(void *data) {
978 LCSipTransports transport;
979 UpnpContext *lupnp = (UpnpContext *)data;
981 ms_mutex_lock(&lupnp->mutex);
984 if(lupnp->state == LinphoneUpnpStateOk) {
985 linphone_core_get_sip_transports(lupnp->lc, &transport);
986 linphone_upnp_update_port_binding(lupnp, &lupnp->sip_udp, UPNP_IGD_IP_PROTOCOL_UDP, transport.udp_port, UPNP_CORE_RETRY_DELAY);
987 linphone_upnp_update_port_binding(lupnp, &lupnp->sip_tcp, UPNP_IGD_IP_PROTOCOL_TCP, transport.tcp_port, UPNP_CORE_RETRY_DELAY);
988 linphone_upnp_update_port_binding(lupnp, &lupnp->sip_tls, UPNP_IGD_IP_PROTOCOL_TCP, transport.tls_port, UPNP_CORE_RETRY_DELAY);
991 linphone_upnp_update_proxy(lupnp, FALSE);
992 linphone_upnp_update_config(lupnp);
994 ms_mutex_unlock(&lupnp->mutex);
998 int linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
1000 SalStreamDescription *stream;
1001 UpnpStream *upnpStream;
1003 for (i = 0; i < desc->n_active_streams; i++) {
1004 stream = &desc->streams[i];
1006 if(stream->type == SalAudio) {
1007 upnpStream = session->audio;
1008 } else if(stream->type == SalVideo) {
1009 upnpStream = session->video;
1011 if(upnpStream != NULL) {
1012 if(upnpStream->rtp != NULL && upnpStream->rtp->state == LinphoneUpnpStateOk) {
1013 strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
1014 stream->rtp_port = upnpStream->rtp->external_port;
1016 if(upnpStream->rtcp != NULL && upnpStream->rtcp->state == LinphoneUpnpStateOk) {
1017 strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
1018 stream->rtcp_port = upnpStream->rtcp->external_port;
1030 UpnpPortBinding *linphone_upnp_port_binding_new() {
1031 UpnpPortBinding *port = NULL;
1032 port = ms_new0(UpnpPortBinding,1);
1033 ms_mutex_init(&port->mutex, NULL);
1034 port->state = LinphoneUpnpStateIdle;
1035 port->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
1036 port->device_id = NULL;
1037 port->local_addr[0] = '\0';
1038 port->local_port = -1;
1039 port->external_addr[0] = '\0';
1040 port->external_port = -1;
1041 port->to_remove = FALSE;
1042 port->to_add = FALSE;
1044 port->last_update = 0;
1048 UpnpPortBinding *linphone_upnp_port_binding_new_with_parameters(upnp_igd_ip_protocol protocol, int local_port, int external_port) {
1049 UpnpPortBinding *port_binding = linphone_upnp_port_binding_new();
1050 port_binding->protocol = protocol;
1051 port_binding->local_port = local_port;
1052 port_binding->external_port = external_port;
1053 return port_binding;
1056 UpnpPortBinding *linphone_upnp_port_binding_new_or_collect(MSList *list, upnp_igd_ip_protocol protocol, int local_port, int external_port) {
1057 UpnpPortBinding *tmp_binding;
1058 UpnpPortBinding *end_binding;
1059 end_binding = linphone_upnp_port_binding_new_with_parameters(protocol, local_port, external_port);
1060 tmp_binding = linphone_upnp_port_binding_equivalent_in_list(list, end_binding);
1061 if(tmp_binding != NULL) {
1062 linphone_upnp_port_binding_release(end_binding);
1063 end_binding = tmp_binding;
1068 UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port) {
1069 UpnpPortBinding *new_port = NULL;
1070 new_port = ms_new0(UpnpPortBinding,1);
1071 memcpy(new_port, port, sizeof(UpnpPortBinding));
1072 new_port->device_id = NULL;
1073 linphone_upnp_port_binding_set_device_id(new_port, port->device_id);
1074 ms_mutex_init(&new_port->mutex, NULL);
1079 void linphone_upnp_port_binding_set_device_id(UpnpPortBinding *port, const char *device_id) {
1080 char *formated_device_id = linphone_upnp_format_device_id(device_id);
1081 if(formated_device_id != NULL && port->device_id != NULL) {
1082 if(strcmp(formated_device_id, port->device_id) == 0) {
1083 ms_free(formated_device_id);
1087 if(port->device_id != NULL) {
1088 ms_free(port->device_id);
1090 port->device_id = formated_device_id;
1093 void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port) {
1094 if(strlen(port->local_addr)) {
1095 ortp_log(level, "uPnP IGD: %s %s|%d->%s:%d (retry %d)", msg,
1096 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
1097 port->external_port,
1102 ortp_log(level, "uPnP IGD: %s %s|%d->%d (retry %d)", msg,
1103 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
1104 port->external_port,
1110 bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
1111 return port1->protocol == port2->protocol &&
1112 port1->local_port == port2->local_port &&
1113 port1->external_port == port2->external_port;
1116 UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port) {
1117 UpnpPortBinding *port_mapping;
1118 while(list != NULL) {
1119 port_mapping = (UpnpPortBinding *)list->data;
1120 if(linphone_upnp_port_binding_equal(port, port_mapping)) {
1121 return port_mapping;
1129 UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port) {
1130 ms_mutex_lock(&port->mutex);
1132 ms_mutex_unlock(&port->mutex);
1136 void linphone_upnp_port_binding_release(UpnpPortBinding *port) {
1137 ms_mutex_lock(&port->mutex);
1138 if(--port->ref == 0) {
1139 if(port->device_id != NULL) {
1140 ms_free(port->device_id);
1142 ms_mutex_unlock(&port->mutex);
1143 ms_mutex_destroy(&port->mutex);
1147 ms_mutex_unlock(&port->mutex);
1155 UpnpStream* linphone_upnp_stream_new() {
1156 UpnpStream *stream = ms_new0(UpnpStream,1);
1157 stream->state = LinphoneUpnpStateIdle;
1159 stream->rtcp = NULL;
1163 void linphone_upnp_stream_destroy(UpnpStream* stream) {
1164 if(stream->rtp != NULL) {
1165 linphone_upnp_port_binding_release(stream->rtp);
1168 if(stream->rtcp != NULL) {
1169 linphone_upnp_port_binding_release(stream->rtcp);
1170 stream->rtcp = NULL;
1180 UpnpSession* linphone_upnp_session_new(LinphoneCall* call) {
1181 UpnpSession *session = ms_new0(UpnpSession,1);
1182 session->call = call;
1183 session->state = LinphoneUpnpStateIdle;
1184 session->audio = linphone_upnp_stream_new();
1185 session->video = linphone_upnp_stream_new();
1189 void linphone_upnp_session_destroy(UpnpSession *session) {
1190 LinphoneCore *lc = session->call->core;
1192 if(lc->upnp != NULL) {
1193 /* Remove bindings */
1194 if(session->audio->rtp != NULL) {
1195 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtp, TRUE);
1197 if(session->audio->rtcp != NULL) {
1198 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtcp, TRUE);
1200 if(session->video->rtp != NULL) {
1201 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtp, TRUE);
1203 if(session->video->rtcp != NULL) {
1204 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtcp, TRUE);
1208 session->call->stats[LINPHONE_CALL_STATS_AUDIO].upnp_state = LinphoneUpnpStateKo;
1209 session->call->stats[LINPHONE_CALL_STATS_VIDEO].upnp_state = LinphoneUpnpStateKo;
1211 linphone_upnp_stream_destroy(session->audio);
1212 linphone_upnp_stream_destroy(session->video);
1216 LinphoneUpnpState linphone_upnp_session_get_state(UpnpSession *session) {
1217 return session->state;
1225 struct linphone_upnp_config_list_port_bindings_struct {
1226 struct _LpConfig *lpc;
1228 const char *device_id;
1231 static void linphone_upnp_config_list_port_bindings_cb(const char *entry, struct linphone_upnp_config_list_port_bindings_struct *cookie) {
1232 char device_id[UPNP_UUID_LEN + 1];
1233 char protocol_str[4]; // TCP or UDP
1234 upnp_igd_ip_protocol protocol;
1238 bool_t valid = TRUE;
1239 UpnpPortBinding *port;
1241 ret = sscanf(entry, "%"UPNP_UUID_LEN_STR"s-%3s-%i-%i", device_id, protocol_str, &external_port, &local_port);
1243 // Handle only wanted device bindings
1244 if(device_id != NULL && strcmp(cookie->device_id, device_id) != 0) {
1247 if(linphone_upnp_strncmpi(protocol_str, "TCP", 3) == 0) {
1248 protocol = UPNP_IGD_IP_PROTOCOL_TCP;
1249 } else if(linphone_upnp_strncmpi(protocol_str, "UDP", 3) == 0) {
1250 protocol = UPNP_IGD_IP_PROTOCOL_UDP;
1255 port = linphone_upnp_port_binding_new();
1256 linphone_upnp_port_binding_set_device_id(port, device_id);
1257 port->state = LinphoneUpnpStateOk;
1258 port->protocol = protocol;
1259 port->external_port = external_port;
1260 port->local_port = local_port;
1261 cookie->retList = ms_list_append(cookie->retList, port);
1267 ms_warning("uPnP configuration invalid line: %s", entry);
1271 MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc, const char *device_id) {
1272 char *formated_device_id = linphone_upnp_format_device_id(device_id);
1273 struct linphone_upnp_config_list_port_bindings_struct cookie = {lpc, NULL, formated_device_id};
1274 lp_config_for_each_entry(lpc, UPNP_SECTION_NAME, (void(*)(const char *, void*))linphone_upnp_config_list_port_bindings_cb, &cookie);
1275 ms_free(formated_device_id);
1276 return cookie.retList;
1279 void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1281 UpnpPortBinding *list_port;
1283 if(port->device_id == NULL) {
1284 ms_error("Can't remove port binding without device_id");
1288 list = lupnp->removing_configs;
1289 while(list != NULL) {
1290 list_port = (UpnpPortBinding *)list->data;
1291 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1292 lupnp->removing_configs = ms_list_remove(lupnp->removing_configs, list_port);
1293 linphone_upnp_port_binding_release(list_port);
1296 list = ms_list_next(list);
1299 list = lupnp->adding_configs;
1300 while(list != NULL) {
1301 list_port = (UpnpPortBinding *)list->data;
1302 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1305 list = ms_list_next(list);
1308 list_port = linphone_upnp_port_binding_copy(port);
1309 lupnp->adding_configs = ms_list_append(lupnp->adding_configs, list_port);
1312 void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1314 UpnpPortBinding *list_port;
1316 if(port->device_id == NULL) {
1317 ms_error("Can't remove port binding without device_id");
1321 list = lupnp->adding_configs;
1322 while(list != NULL) {
1323 list_port = (UpnpPortBinding *)list->data;
1324 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1325 lupnp->adding_configs = ms_list_remove(lupnp->adding_configs, list_port);
1326 linphone_upnp_port_binding_release(list_port);
1329 list = ms_list_next(list);
1332 list = lupnp->removing_configs;
1333 while(list != NULL) {
1334 list_port = (UpnpPortBinding *)list->data;
1335 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1338 list = ms_list_next(list);
1341 list_port = linphone_upnp_port_binding_copy(port);
1342 lupnp->removing_configs = ms_list_append(lupnp->removing_configs, list_port);