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) {
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 mapping.remote_port = rand()%(0xffff - 1024) + 1024;
558 mapping.remote_port = port->external_port;
559 mapping.remote_host = "";
560 snprintf(description, 128, "%s %s at %s:%d",
562 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP": "UDP",
563 port->local_addr, port->local_port);
564 mapping.description = description;
565 mapping.protocol = port->protocol;
568 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to add port binding", port);
569 ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
572 port->state = LinphoneUpnpStateKo;
577 int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry) {
578 upnp_igd_port_mapping mapping;
581 if(lupnp->state != LinphoneUpnpStateOk) {
585 // Compute port binding state
586 if(port->state != LinphoneUpnpStateRemoving) {
587 port->to_add = FALSE;
588 switch(port->state) {
589 case LinphoneUpnpStateOk: {
591 port->state = LinphoneUpnpStateRemoving;
594 case LinphoneUpnpStateAdding: {
595 port->to_remove = TRUE;
604 // No retry if specified
605 if(port->retry != 0 && !retry) {
609 if(port->retry >= UPNP_REMOVE_MAX_RETRY) {
612 linphone_upnp_port_binding_set_device_id(port, upnp_igd_get_device_id(lupnp->upnp_igd_ctxt));
613 mapping.cookie = linphone_upnp_port_binding_retain(port);
614 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
616 mapping.remote_port = port->external_port;
617 mapping.remote_host = "";
618 mapping.protocol = port->protocol;
620 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to remove port binding", port);
621 ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
624 port->state = LinphoneUpnpStateKo;
630 * uPnP Core interfaces
633 int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool_t video) {
634 LinphoneCore *lc = call->core;
635 UpnpContext *lupnp = lc->upnp;
642 ms_mutex_lock(&lupnp->mutex);
644 // Don't handle when the call
645 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
651 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->audio->rtp,
652 UPNP_IGD_IP_PROTOCOL_UDP, (audio)? call->audio_port:0, UPNP_CALL_RETRY_DELAY);
654 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->audio->rtcp,
655 UPNP_IGD_IP_PROTOCOL_UDP, (audio)? call->audio_port+1:0, UPNP_CALL_RETRY_DELAY);
660 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->video->rtp,
661 UPNP_IGD_IP_PROTOCOL_UDP, (video)? call->video_port:0, UPNP_CALL_RETRY_DELAY);
663 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->video->rtcp,
664 UPNP_IGD_IP_PROTOCOL_UDP, (video)? call->video_port+1:0, UPNP_CALL_RETRY_DELAY);
667 ms_mutex_unlock(&lupnp->mutex);
670 * Update uPnP call state
672 linphone_upnp_call_process(call);
679 int linphone_core_update_upnp_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) {
680 bool_t audio = FALSE;
681 bool_t video = FALSE;
683 const SalStreamDescription *stream;
685 for (i = 0; i < md->n_total_streams; i++) {
686 stream = &md->streams[i];
687 if(stream->type == SalAudio) {
689 } else if(stream->type == SalVideo) {
694 return linphone_core_update_upnp_audio_video(call, audio, video);
697 int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
698 return linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
701 void linphone_core_update_upnp_state_in_call_stats(LinphoneCall *call) {
702 call->stats[LINPHONE_CALL_STATS_AUDIO].upnp_state = call->upnp_session->audio->state;
703 call->stats[LINPHONE_CALL_STATS_VIDEO].upnp_state = call->upnp_session->video->state;
706 void linphone_upnp_update_stream_state(UpnpStream *stream) {
707 if((stream->rtp == NULL || stream->rtp->state == LinphoneUpnpStateOk || stream->rtp->state == LinphoneUpnpStateIdle) &&
708 (stream->rtcp == NULL || stream->rtcp->state == LinphoneUpnpStateOk || stream->rtcp->state == LinphoneUpnpStateIdle)) {
709 stream->state = LinphoneUpnpStateOk;
710 } else if((stream->rtp != NULL &&
711 (stream->rtp->state == LinphoneUpnpStateAdding || stream->rtp->state == LinphoneUpnpStateRemoving)) ||
712 (stream->rtcp != NULL &&
713 (stream->rtcp->state == LinphoneUpnpStateAdding || stream->rtcp->state == LinphoneUpnpStateRemoving))) {
714 stream->state = LinphoneUpnpStatePending;
715 } else if((stream->rtp != NULL && stream->rtp->state == LinphoneUpnpStateKo) ||
716 (stream->rtcp != NULL && stream->rtcp->state == LinphoneUpnpStateKo)) {
717 stream->state = LinphoneUpnpStateKo;
719 ms_error("Invalid stream %p state", stream);
723 int linphone_upnp_call_process(LinphoneCall *call) {
724 LinphoneCore *lc = call->core;
725 UpnpContext *lupnp = lc->upnp;
727 LinphoneUpnpState oldState = 0, newState = 0;
733 ms_mutex_lock(&lupnp->mutex);
735 // Don't handle when the call
736 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
742 linphone_upnp_update_stream_state(call->upnp_session->audio);
747 linphone_upnp_update_stream_state(call->upnp_session->video);
752 linphone_core_update_upnp_state_in_call_stats(call);
755 * Update session state
757 oldState = call->upnp_session->state;
758 if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
759 call->upnp_session->video->state == LinphoneUpnpStateOk) {
760 call->upnp_session->state = LinphoneUpnpStateOk;
761 } else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
762 call->upnp_session->video->state == LinphoneUpnpStatePending) {
763 call->upnp_session->state = LinphoneUpnpStatePending;
764 } else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
765 call->upnp_session->video->state == LinphoneUpnpStateKo) {
766 call->upnp_session->state = LinphoneUpnpStateKo;
768 call->upnp_session->state = LinphoneUpnpStateIdle;
770 newState = call->upnp_session->state;
773 ms_mutex_unlock(&lupnp->mutex);
775 /* When change is done proceed update */
776 if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
777 (newState == LinphoneUpnpStateOk || newState == LinphoneUpnpStateKo)) {
778 if(call->upnp_session->state == LinphoneUpnpStateOk)
779 ms_message("uPnP IGD: uPnP for Call %p is ok", call);
781 ms_message("uPnP IGD: uPnP for Call %p is ko", call);
783 switch (call->state) {
784 case LinphoneCallUpdating:
785 linphone_core_start_update_call(lc, call);
787 case LinphoneCallUpdatedByRemote:
788 linphone_core_start_accept_call_update(lc, call);
790 case LinphoneCallOutgoingInit:
791 linphone_core_proceed_with_invite_if_ready(lc, call, NULL);
793 case LinphoneCallIdle:
794 linphone_core_notify_incoming_call(lc, call);
804 void linphone_core_upnp_refresh(UpnpContext *lupnp) {
805 MSList *global_list = NULL;
809 UpnpPortBinding *port_mapping, *port_mapping2;
811 ms_message("uPnP IGD: Refresh mappings");
813 if(lupnp->sip_udp != NULL) {
814 global_list = ms_list_append(global_list, lupnp->sip_udp);
816 if(lupnp->sip_tcp != NULL) {
817 global_list = ms_list_append(global_list, lupnp->sip_tcp);
819 if(lupnp->sip_tls != NULL) {
820 global_list = ms_list_append(global_list, lupnp->sip_tls);
823 list = lupnp->lc->calls;
824 while(list != NULL) {
825 call = (LinphoneCall *)list->data;
826 if(call->upnp_session != NULL) {
827 if(call->upnp_session->audio->rtp != NULL) {
828 global_list = ms_list_append(global_list, call->upnp_session->audio->rtp);
830 if(call->upnp_session->audio->rtcp != NULL) {
831 global_list = ms_list_append(global_list, call->upnp_session->audio->rtcp);
833 if(call->upnp_session->video->rtp != NULL) {
834 global_list = ms_list_append(global_list, call->upnp_session->video->rtp);
836 if(call->upnp_session->video->rtcp != NULL) {
837 global_list = ms_list_append(global_list, call->upnp_session->video->rtcp);
843 list = linphone_upnp_config_list_port_bindings(lupnp->lc->config, upnp_igd_get_device_id(lupnp->upnp_igd_ctxt));
844 for(item = list;item != NULL; item = item->next) {
845 port_mapping = (UpnpPortBinding *)item->data;
846 port_mapping2 = linphone_upnp_port_binding_equivalent_in_list(global_list, port_mapping);
847 if(port_mapping2 == NULL) {
848 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE);
849 } else if(port_mapping2->state == LinphoneUpnpStateIdle){
850 /* Force to remove */
851 port_mapping2->state = LinphoneUpnpStateOk;
854 ms_list_for_each(list, (void (*)(void*))linphone_upnp_port_binding_release);
855 list = ms_list_free(list);
858 // (Re)Add removed port bindings
860 while(list != NULL) {
861 port_mapping = (UpnpPortBinding *)list->data;
862 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE);
863 linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, TRUE);
866 global_list = ms_list_free(global_list);
869 void linphone_upnp_update_port_binding(UpnpContext *lupnp, UpnpPortBinding **port_mapping, upnp_igd_ip_protocol protocol, int port, int retry_delay) {
870 const char *local_addr, *external_addr;
871 time_t now = time(NULL);
873 if(*port_mapping != NULL) {
874 if(port != (*port_mapping)->local_port) {
875 linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
876 *port_mapping = NULL;
879 if(*port_mapping == NULL) {
880 *port_mapping = linphone_upnp_port_binding_new_or_collect(lupnp->pending_bindings, protocol, port, port);
884 local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
885 external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
887 // Force binding update on local address change
888 if(local_addr != NULL) {
889 if(strncmp((*port_mapping)->local_addr, local_addr, sizeof((*port_mapping)->local_addr))) {
890 linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
891 strncpy((*port_mapping)->local_addr, local_addr, sizeof((*port_mapping)->local_addr));
894 if(external_addr != NULL) {
895 strncpy((*port_mapping)->external_addr, external_addr, sizeof((*port_mapping)->external_addr));
898 // Add (if not already done) the binding
899 if(now - (*port_mapping)->last_update >= retry_delay) {
900 (*port_mapping)->last_update = now;
901 linphone_upnp_context_send_add_port_binding(lupnp, *port_mapping, FALSE);
904 if(*port_mapping != NULL) {
905 linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
906 *port_mapping = NULL;
911 void linphone_upnp_update_config(UpnpContext* lupnp) {
914 UpnpPortBinding *port_mapping;
917 for(item = lupnp->adding_configs;item!=NULL;item=item->next) {
918 port_mapping = (UpnpPortBinding *)item->data;
919 snprintf(key, sizeof(key), "%s-%s-%d-%d",
920 port_mapping->device_id,
921 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
922 port_mapping->external_port,
923 port_mapping->local_port);
924 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, "uPnP");
925 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping);
927 ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
928 lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
931 for(item = lupnp->removing_configs;item!=NULL;item=item->next) {
932 port_mapping = (UpnpPortBinding *)item->data;
933 snprintf(key, sizeof(key), "%s-%s-%d-%d",
934 port_mapping->device_id,
935 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
936 port_mapping->external_port,
937 port_mapping->local_port);
938 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, NULL);
939 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping);
941 ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
942 lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
945 void linphone_upnp_update_proxy(UpnpContext* lupnp, bool_t force) {
946 LinphoneUpnpState ready_state;
948 time_t now = (force)? (lupnp->last_ready_check + UPNP_CORE_READY_CHECK) : time(NULL);
950 /* Refresh registers if we are ready */
951 if(now - lupnp->last_ready_check >= UPNP_CORE_READY_CHECK) {
952 lupnp->last_ready_check = now;
953 ready_state = (_linphone_upnp_context_is_ready_for_register(lupnp))? LinphoneUpnpStateOk: LinphoneUpnpStateKo;
954 if(ready_state != lupnp->last_ready_state) {
955 for(item=linphone_core_get_proxy_config_list(lupnp->lc);item!=NULL;item=item->next) {
956 LinphoneProxyConfig *cfg=(LinphoneProxyConfig*)item->data;
957 if (linphone_proxy_config_register_enabled(cfg)) {
958 if (ready_state != LinphoneUpnpStateOk) {
959 // Only reset ithe registration if we require that upnp should be ok
960 if(lupnp->lc->sip_conf.register_only_when_upnp_is_ok) {
961 linphone_proxy_config_set_state(cfg, LinphoneRegistrationNone, "Registration impossible (uPnP not ready)");
970 lupnp->last_ready_state = ready_state;
975 bool_t linphone_core_upnp_hook(void *data) {
976 LCSipTransports transport;
977 UpnpContext *lupnp = (UpnpContext *)data;
979 ms_mutex_lock(&lupnp->mutex);
982 if(lupnp->state == LinphoneUpnpStateOk) {
983 linphone_core_get_sip_transports(lupnp->lc, &transport);
984 linphone_upnp_update_port_binding(lupnp, &lupnp->sip_udp, UPNP_IGD_IP_PROTOCOL_UDP, transport.udp_port, UPNP_CORE_RETRY_DELAY);
985 linphone_upnp_update_port_binding(lupnp, &lupnp->sip_tcp, UPNP_IGD_IP_PROTOCOL_TCP, transport.tcp_port, UPNP_CORE_RETRY_DELAY);
986 linphone_upnp_update_port_binding(lupnp, &lupnp->sip_tls, UPNP_IGD_IP_PROTOCOL_TCP, transport.tls_port, UPNP_CORE_RETRY_DELAY);
989 linphone_upnp_update_proxy(lupnp, FALSE);
990 linphone_upnp_update_config(lupnp);
992 ms_mutex_unlock(&lupnp->mutex);
996 int linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
998 SalStreamDescription *stream;
999 UpnpStream *upnpStream;
1001 for (i = 0; i < desc->n_active_streams; i++) {
1002 stream = &desc->streams[i];
1004 if(stream->type == SalAudio) {
1005 upnpStream = session->audio;
1006 } else if(stream->type == SalVideo) {
1007 upnpStream = session->video;
1009 if(upnpStream != NULL) {
1010 if(upnpStream->rtp != NULL && upnpStream->rtp->state == LinphoneUpnpStateOk) {
1011 strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
1012 stream->rtp_port = upnpStream->rtp->external_port;
1014 if(upnpStream->rtcp != NULL && upnpStream->rtcp->state == LinphoneUpnpStateOk) {
1015 strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
1016 stream->rtcp_port = upnpStream->rtcp->external_port;
1028 UpnpPortBinding *linphone_upnp_port_binding_new() {
1029 UpnpPortBinding *port = NULL;
1030 port = ms_new0(UpnpPortBinding,1);
1031 ms_mutex_init(&port->mutex, NULL);
1032 port->state = LinphoneUpnpStateIdle;
1033 port->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
1034 port->device_id = NULL;
1035 port->local_addr[0] = '\0';
1036 port->local_port = -1;
1037 port->external_addr[0] = '\0';
1038 port->external_port = -1;
1039 port->to_remove = FALSE;
1040 port->to_add = FALSE;
1042 port->last_update = 0;
1046 UpnpPortBinding *linphone_upnp_port_binding_new_with_parameters(upnp_igd_ip_protocol protocol, int local_port, int external_port) {
1047 UpnpPortBinding *port_binding = linphone_upnp_port_binding_new();
1048 port_binding->protocol = protocol;
1049 port_binding->local_port = local_port;
1050 port_binding->external_port = external_port;
1051 return port_binding;
1054 UpnpPortBinding *linphone_upnp_port_binding_new_or_collect(MSList *list, upnp_igd_ip_protocol protocol, int local_port, int external_port) {
1055 UpnpPortBinding *tmp_binding;
1056 UpnpPortBinding *end_binding;
1057 end_binding = linphone_upnp_port_binding_new_with_parameters(protocol, local_port, external_port);
1058 tmp_binding = linphone_upnp_port_binding_equivalent_in_list(list, end_binding);
1059 if(tmp_binding != NULL) {
1060 linphone_upnp_port_binding_release(end_binding);
1061 end_binding = tmp_binding;
1066 UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port) {
1067 UpnpPortBinding *new_port = NULL;
1068 new_port = ms_new0(UpnpPortBinding,1);
1069 memcpy(new_port, port, sizeof(UpnpPortBinding));
1070 new_port->device_id = NULL;
1071 linphone_upnp_port_binding_set_device_id(new_port, port->device_id);
1072 ms_mutex_init(&new_port->mutex, NULL);
1077 void linphone_upnp_port_binding_set_device_id(UpnpPortBinding *port, const char *device_id) {
1078 char *formated_device_id = linphone_upnp_format_device_id(device_id);
1079 if(formated_device_id != NULL && port->device_id != NULL) {
1080 if(strcmp(formated_device_id, port->device_id) == 0) {
1081 ms_free(formated_device_id);
1085 if(port->device_id != NULL) {
1086 ms_free(port->device_id);
1088 port->device_id = formated_device_id;
1091 void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port) {
1092 if(strlen(port->local_addr)) {
1093 ortp_log(level, "uPnP IGD: %s %s|%d->%s:%d (retry %d)", msg,
1094 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
1095 port->external_port,
1100 ortp_log(level, "uPnP IGD: %s %s|%d->%d (retry %d)", msg,
1101 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
1102 port->external_port,
1108 bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
1109 return port1->protocol == port2->protocol &&
1110 port1->local_port == port2->local_port &&
1111 port1->external_port == port2->external_port;
1114 UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port) {
1115 UpnpPortBinding *port_mapping;
1116 while(list != NULL) {
1117 port_mapping = (UpnpPortBinding *)list->data;
1118 if(linphone_upnp_port_binding_equal(port, port_mapping)) {
1119 return port_mapping;
1127 UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port) {
1128 ms_mutex_lock(&port->mutex);
1130 ms_mutex_unlock(&port->mutex);
1134 void linphone_upnp_port_binding_release(UpnpPortBinding *port) {
1135 ms_mutex_lock(&port->mutex);
1136 if(--port->ref == 0) {
1137 if(port->device_id != NULL) {
1138 ms_free(port->device_id);
1140 ms_mutex_unlock(&port->mutex);
1141 ms_mutex_destroy(&port->mutex);
1145 ms_mutex_unlock(&port->mutex);
1153 UpnpStream* linphone_upnp_stream_new() {
1154 UpnpStream *stream = ms_new0(UpnpStream,1);
1155 stream->state = LinphoneUpnpStateIdle;
1157 stream->rtcp = NULL;
1161 void linphone_upnp_stream_destroy(UpnpStream* stream) {
1162 if(stream->rtp != NULL) {
1163 linphone_upnp_port_binding_release(stream->rtp);
1166 if(stream->rtcp != NULL) {
1167 linphone_upnp_port_binding_release(stream->rtcp);
1168 stream->rtcp = NULL;
1178 UpnpSession* linphone_upnp_session_new(LinphoneCall* call) {
1179 UpnpSession *session = ms_new0(UpnpSession,1);
1180 session->call = call;
1181 session->state = LinphoneUpnpStateIdle;
1182 session->audio = linphone_upnp_stream_new();
1183 session->video = linphone_upnp_stream_new();
1187 void linphone_upnp_session_destroy(UpnpSession *session) {
1188 LinphoneCore *lc = session->call->core;
1190 if(lc->upnp != NULL) {
1191 /* Remove bindings */
1192 if(session->audio->rtp != NULL) {
1193 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtp, TRUE);
1195 if(session->audio->rtcp != NULL) {
1196 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtcp, TRUE);
1198 if(session->video->rtp != NULL) {
1199 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtp, TRUE);
1201 if(session->video->rtcp != NULL) {
1202 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtcp, TRUE);
1206 session->call->stats[LINPHONE_CALL_STATS_AUDIO].upnp_state = LinphoneUpnpStateKo;
1207 session->call->stats[LINPHONE_CALL_STATS_VIDEO].upnp_state = LinphoneUpnpStateKo;
1209 linphone_upnp_stream_destroy(session->audio);
1210 linphone_upnp_stream_destroy(session->video);
1214 LinphoneUpnpState linphone_upnp_session_get_state(UpnpSession *session) {
1215 return session->state;
1223 struct linphone_upnp_config_list_port_bindings_struct {
1224 struct _LpConfig *lpc;
1226 const char *device_id;
1229 static void linphone_upnp_config_list_port_bindings_cb(const char *entry, struct linphone_upnp_config_list_port_bindings_struct *cookie) {
1230 char device_id[UPNP_UUID_LEN + 1];
1231 char protocol_str[4]; // TCP or UDP
1232 upnp_igd_ip_protocol protocol;
1236 bool_t valid = TRUE;
1237 UpnpPortBinding *port;
1239 ret = sscanf(entry, "%"UPNP_UUID_LEN_STR"s-%3s-%i-%i", device_id, protocol_str, &external_port, &local_port);
1241 // Handle only wanted device bindings
1242 if(device_id != NULL && strcmp(cookie->device_id, device_id) != 0) {
1245 if(linphone_upnp_strncmpi(protocol_str, "TCP", 3) == 0) {
1246 protocol = UPNP_IGD_IP_PROTOCOL_TCP;
1247 } else if(linphone_upnp_strncmpi(protocol_str, "UDP", 3) == 0) {
1248 protocol = UPNP_IGD_IP_PROTOCOL_UDP;
1253 port = linphone_upnp_port_binding_new();
1254 linphone_upnp_port_binding_set_device_id(port, device_id);
1255 port->state = LinphoneUpnpStateOk;
1256 port->protocol = protocol;
1257 port->external_port = external_port;
1258 port->local_port = local_port;
1259 cookie->retList = ms_list_append(cookie->retList, port);
1265 ms_warning("uPnP configuration invalid line: %s", entry);
1269 MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc, const char *device_id) {
1270 char *formated_device_id = linphone_upnp_format_device_id(device_id);
1271 struct linphone_upnp_config_list_port_bindings_struct cookie = {lpc, NULL, formated_device_id};
1272 lp_config_for_each_entry(lpc, UPNP_SECTION_NAME, (void(*)(const char *, void*))linphone_upnp_config_list_port_bindings_cb, &cookie);
1273 ms_free(formated_device_id);
1274 return cookie.retList;
1277 void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1279 UpnpPortBinding *list_port;
1281 if(port->device_id == NULL) {
1282 ms_error("Can't remove port binding without device_id");
1286 list = lupnp->removing_configs;
1287 while(list != NULL) {
1288 list_port = (UpnpPortBinding *)list->data;
1289 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1290 lupnp->removing_configs = ms_list_remove(lupnp->removing_configs, list_port);
1291 linphone_upnp_port_binding_release(list_port);
1294 list = ms_list_next(list);
1297 list = lupnp->adding_configs;
1298 while(list != NULL) {
1299 list_port = (UpnpPortBinding *)list->data;
1300 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1303 list = ms_list_next(list);
1306 list_port = linphone_upnp_port_binding_copy(port);
1307 lupnp->adding_configs = ms_list_append(lupnp->adding_configs, list_port);
1310 void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1312 UpnpPortBinding *list_port;
1314 if(port->device_id == NULL) {
1315 ms_error("Can't remove port binding without device_id");
1319 list = lupnp->adding_configs;
1320 while(list != NULL) {
1321 list_port = (UpnpPortBinding *)list->data;
1322 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1323 lupnp->adding_configs = ms_list_remove(lupnp->adding_configs, list_port);
1324 linphone_upnp_port_binding_release(list_port);
1327 list = ms_list_next(list);
1330 list = lupnp->removing_configs;
1331 while(list != NULL) {
1332 list_port = (UpnpPortBinding *)list->data;
1333 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1336 list = ms_list_next(list);
1339 list_port = linphone_upnp_port_binding_copy(port);
1340 lupnp->removing_configs = ms_list_append(lupnp->removing_configs, list_port);