3 Copyright (C) 2012 Belledonne Communications SARL
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 #define UPNP_ADD_MAX_RETRY 4
25 #define UPNP_REMOVE_MAX_RETRY 4
26 #define UPNP_SECTION_NAME "uPnP"
27 #define UPNP_CORE_READY_CHECK 1
28 #define UPNP_CORE_RETRY_DELAY 4
29 #define UPNP_CALL_RETRY_DELAY 1
35 typedef struct _UpnpPortBinding {
37 LinphoneUpnpState state;
38 upnp_igd_ip_protocol protocol;
39 char local_addr[LINPHONE_IPADDR_SIZE];
41 char external_addr[LINPHONE_IPADDR_SIZE];
50 typedef struct _UpnpStream {
52 UpnpPortBinding *rtcp;
53 LinphoneUpnpState state;
60 LinphoneUpnpState state;
65 upnp_igd_context *upnp_igd_ctxt;
66 UpnpPortBinding *sip_tcp;
67 UpnpPortBinding *sip_tls;
68 UpnpPortBinding *sip_udp;
69 LinphoneUpnpState state;
70 MSList *removing_configs;
71 MSList *adding_configs;
72 MSList *pending_bindings;
77 time_t last_ready_check;
78 LinphoneUpnpState last_ready_state;
82 bool_t linphone_core_upnp_hook(void *data);
83 void linphone_core_upnp_refresh(UpnpContext *ctx);
85 UpnpPortBinding *linphone_upnp_port_binding_new();
86 UpnpPortBinding *linphone_upnp_port_binding_new_with_parameters(upnp_igd_ip_protocol protocol, int local_port, int external_port);
87 UpnpPortBinding *linphone_upnp_port_binding_new_or_collect(MSList *list, upnp_igd_ip_protocol protocol, int local_port, int external_port);
88 UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port);
89 bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2);
90 UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port);
91 UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port);
92 void linphone_upnp_update_port_binding(UpnpContext *lupnp, UpnpPortBinding **port_mapping, upnp_igd_ip_protocol protocol, int port, int retry_delay);
93 void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port);
94 void linphone_upnp_port_binding_release(UpnpPortBinding *port);
95 void linphone_upnp_update_config(UpnpContext *lupnp);
96 void linphone_upnp_update_proxy(UpnpContext *lupnp, bool_t force);
99 MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc);
100 void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port);
101 void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port);
104 int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry);
105 int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry);
112 /* Convert uPnP IGD logs to ortp logs */
113 void linphone_upnp_igd_print(void *cookie, upnp_igd_print_level level, const char *fmt, va_list list) {
114 int ortp_level = ORTP_DEBUG;
116 case UPNP_IGD_MESSAGE:
117 ortp_level = ORTP_MESSAGE;
119 case UPNP_IGD_WARNING:
120 ortp_level = ORTP_DEBUG; // Too verbose otherwise
123 ortp_level = ORTP_DEBUG; // Too verbose otherwise
128 ortp_logv(ortp_level, fmt, list);
131 void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) {
132 UpnpContext *lupnp = (UpnpContext *)cookie;
133 upnp_igd_port_mapping *mapping = NULL;
134 UpnpPortBinding *port_mapping = NULL;
135 const char *ip_address = NULL;
136 const char *connection_status = NULL;
137 bool_t nat_enabled = FALSE;
138 LinphoneUpnpState old_state;
140 if(lupnp == NULL || lupnp->upnp_igd_ctxt == NULL) {
141 ms_error("uPnP IGD: Invalid context in callback");
145 ms_mutex_lock(&lupnp->mutex);
146 old_state = lupnp->state;
149 case UPNP_IGD_DEVICE_ADDED:
150 case UPNP_IGD_DEVICE_REMOVED:
151 case UPNP_IGD_EXTERNAL_IPADDRESS_CHANGED:
152 case UPNP_IGD_NAT_ENABLED_CHANGED:
153 case UPNP_IGD_CONNECTION_STATUS_CHANGED:
154 ip_address = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
155 connection_status = upnp_igd_get_connection_status(lupnp->upnp_igd_ctxt);
156 nat_enabled = upnp_igd_get_nat_enabled(lupnp->upnp_igd_ctxt);
158 if(ip_address == NULL || connection_status == NULL) {
159 ms_message("uPnP IGD: Pending");
160 lupnp->state = LinphoneUpnpStatePending;
161 } else if(strcasecmp(connection_status, "Connected") || !nat_enabled) {
162 ms_message("uPnP IGD: Not Available");
163 lupnp->state = LinphoneUpnpStateNotAvailable;
165 ms_message("uPnP IGD: Connected");
166 lupnp->state = LinphoneUpnpStateOk;
167 if(old_state != LinphoneUpnpStateOk) {
168 linphone_core_upnp_refresh(lupnp);
174 case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS:
175 mapping = (upnp_igd_port_mapping *) arg;
176 port_mapping = (UpnpPortBinding*) mapping->cookie;
177 port_mapping->external_port = mapping->remote_port;
178 port_mapping->state = LinphoneUpnpStateOk;
179 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Added port binding", port_mapping);
180 linphone_upnp_config_add_port_binding(lupnp, port_mapping);
184 case UPNP_IGD_PORT_MAPPING_ADD_FAILURE:
185 mapping = (upnp_igd_port_mapping *) arg;
186 port_mapping = (UpnpPortBinding*) mapping->cookie;
187 port_mapping->external_port = -1; //Force random external port
188 if(linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, TRUE) != 0) {
189 linphone_upnp_port_binding_log(ORTP_ERROR, "Can't add port binding", port_mapping);
194 case UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS:
195 mapping = (upnp_igd_port_mapping *) arg;
196 port_mapping = (UpnpPortBinding*) mapping->cookie;
197 port_mapping->state = LinphoneUpnpStateIdle;
198 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Removed port binding", port_mapping);
199 linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
203 case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE:
204 mapping = (upnp_igd_port_mapping *) arg;
205 port_mapping = (UpnpPortBinding*) mapping->cookie;
206 if(linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE) != 0) {
207 linphone_upnp_port_binding_log(ORTP_ERROR, "Can't remove port binding", port_mapping);
208 linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
217 if(port_mapping != NULL) {
219 * Execute delayed actions
221 if(port_mapping->to_remove) {
222 if(port_mapping->state == LinphoneUpnpStateOk) {
223 port_mapping->to_remove = FALSE;
224 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, FALSE);
225 } else if(port_mapping->state == LinphoneUpnpStateKo) {
226 port_mapping->to_remove = FALSE;
229 if(port_mapping->to_add) {
230 if(port_mapping->state == LinphoneUpnpStateIdle || port_mapping->state == LinphoneUpnpStateKo) {
231 port_mapping->to_add = FALSE;
232 linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, FALSE);
236 lupnp->pending_bindings = ms_list_remove(lupnp->pending_bindings, port_mapping);
237 linphone_upnp_port_binding_release(port_mapping);
241 * If there is no pending binding emit a signal
243 if(lupnp->pending_bindings == NULL) {
244 pthread_cond_signal(&lupnp->empty_cond);
246 ms_mutex_unlock(&lupnp->mutex);
254 UpnpContext* linphone_upnp_context_new(LinphoneCore *lc) {
255 UpnpContext *lupnp = (UpnpContext *)ms_new0(UpnpContext,1);
257 ms_mutex_init(&lupnp->mutex, NULL);
258 ms_cond_init(&lupnp->empty_cond, NULL);
260 lupnp->last_ready_check = 0;
261 lupnp->last_ready_state = LinphoneUpnpStateIdle;
264 lupnp->pending_bindings = NULL;
265 lupnp->adding_configs = NULL;
266 lupnp->removing_configs = NULL;
267 lupnp->state = LinphoneUpnpStateIdle;
268 ms_message("uPnP IGD: New %p for core %p", lupnp, lc);
271 lupnp->sip_udp = NULL;
272 lupnp->sip_tcp = NULL;
273 lupnp->sip_tls = NULL;
275 linphone_core_add_iterate_hook(lc, linphone_core_upnp_hook, lupnp);
277 lupnp->upnp_igd_ctxt = NULL;
278 lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, lupnp);
279 if(lupnp->upnp_igd_ctxt == NULL) {
280 lupnp->state = LinphoneUpnpStateKo;
281 ms_error("Can't create uPnP IGD context");
285 lupnp->state = LinphoneUpnpStatePending;
286 upnp_igd_start(lupnp->upnp_igd_ctxt);
291 void linphone_upnp_context_destroy(UpnpContext *lupnp) {
292 linphone_core_remove_iterate_hook(lupnp->lc, linphone_core_upnp_hook, lupnp);
294 ms_mutex_lock(&lupnp->mutex);
296 /* Send port binding removes */
297 if(lupnp->sip_udp != NULL) {
298 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_udp, TRUE);
300 if(lupnp->sip_tcp != NULL) {
301 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tcp, TRUE);
303 if(lupnp->sip_tls != NULL) {
304 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tls, TRUE);
307 /* Wait all pending bindings are done */
308 if(lupnp->pending_bindings != NULL) {
309 ms_message("uPnP IGD: Wait all pending port bindings ...");
310 ms_cond_wait(&lupnp->empty_cond, &lupnp->mutex);
313 if(lupnp->upnp_igd_ctxt != NULL) {
314 // upnp_igd_destroy is synchronous so the callbacks will be called in the same thread.
315 // So release the mutex before upnp_igd_destroy call.
316 ms_mutex_unlock(&lupnp->mutex);
317 upnp_igd_destroy(lupnp->upnp_igd_ctxt);
318 ms_mutex_lock(&lupnp->mutex);
319 lupnp->upnp_igd_ctxt = NULL;
322 /* Run one more time configuration update and proxy */
323 linphone_upnp_update_config(lupnp);
324 linphone_upnp_update_proxy(lupnp, TRUE);
326 /* Release port bindings */
327 if(lupnp->sip_udp != NULL) {
328 linphone_upnp_port_binding_release(lupnp->sip_udp);
329 lupnp->sip_udp = NULL;
331 if(lupnp->sip_tcp != NULL) {
332 linphone_upnp_port_binding_release(lupnp->sip_tcp);
333 lupnp->sip_tcp = NULL;
335 if(lupnp->sip_tls != NULL) {
336 linphone_upnp_port_binding_release(lupnp->sip_tls);
337 lupnp->sip_tcp = NULL;
341 ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
342 lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
343 ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
344 lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
345 ms_list_for_each(lupnp->pending_bindings,(void (*)(void*))linphone_upnp_port_binding_release);
346 lupnp->pending_bindings = ms_list_free(lupnp->pending_bindings);
348 ms_mutex_unlock(&lupnp->mutex);
350 ms_mutex_destroy(&lupnp->mutex);
351 ms_cond_destroy(&lupnp->empty_cond);
353 ms_message("uPnP IGD: destroy %p", lupnp);
357 LinphoneUpnpState linphone_upnp_context_get_state(UpnpContext *lupnp) {
358 LinphoneUpnpState state = LinphoneUpnpStateKo;
360 ms_mutex_lock(&lupnp->mutex);
361 state = lupnp->state;
362 ms_mutex_unlock(&lupnp->mutex);
367 bool_t _linphone_upnp_context_is_ready_for_register(UpnpContext *lupnp) {
370 // 1 Check global uPnP state
371 ready = (lupnp->state == LinphoneUpnpStateOk);
373 // 2 Check external ip address
375 if (upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt) == NULL) {
380 // 3 Check sip ports bindings
382 if(lupnp->sip_udp != NULL) {
383 if(lupnp->sip_udp->state != LinphoneUpnpStateOk) {
386 } else if(lupnp->sip_tcp != NULL) {
387 if(lupnp->sip_tcp->state != LinphoneUpnpStateOk) {
390 } else if(lupnp->sip_tls != NULL) {
391 if(lupnp->sip_tls->state != LinphoneUpnpStateOk) {
402 bool_t linphone_upnp_context_is_ready_for_register(UpnpContext *lupnp) {
403 bool_t ready = FALSE;
405 ms_mutex_lock(&lupnp->mutex);
406 ready = _linphone_upnp_context_is_ready_for_register(lupnp);
407 ms_mutex_unlock(&lupnp->mutex);
412 int linphone_upnp_context_get_external_port(UpnpContext *lupnp) {
415 ms_mutex_lock(&lupnp->mutex);
417 if(lupnp->sip_udp != NULL) {
418 if(lupnp->sip_udp->state == LinphoneUpnpStateOk) {
419 port = lupnp->sip_udp->external_port;
421 } else if(lupnp->sip_tcp != NULL) {
422 if(lupnp->sip_tcp->state == LinphoneUpnpStateOk) {
423 port = lupnp->sip_tcp->external_port;
425 } else if(lupnp->sip_tls != NULL) {
426 if(lupnp->sip_tls->state == LinphoneUpnpStateOk) {
427 port = lupnp->sip_tls->external_port;
431 ms_mutex_unlock(&lupnp->mutex);
436 const char* linphone_upnp_context_get_external_ipaddress(UpnpContext *lupnp) {
437 const char* addr = NULL;
439 ms_mutex_lock(&lupnp->mutex);
440 addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
441 ms_mutex_unlock(&lupnp->mutex);
446 int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry) {
447 upnp_igd_port_mapping mapping;
448 char description[128];
451 if(lupnp->state != LinphoneUpnpStateOk) {
455 // Compute port binding state
456 if(port->state != LinphoneUpnpStateAdding) {
457 port->to_remove = FALSE;
458 switch(port->state) {
459 case LinphoneUpnpStateKo:
460 case LinphoneUpnpStateIdle: {
462 port->state = LinphoneUpnpStateAdding;
465 case LinphoneUpnpStateRemoving: {
475 // No retry if specified
476 if(port->retry != 0 && !retry) {
480 if(port->retry >= UPNP_ADD_MAX_RETRY) {
483 mapping.cookie = linphone_upnp_port_binding_retain(port);
484 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
486 mapping.local_port = port->local_port;
487 mapping.local_host = port->local_addr;
488 if(port->external_port == -1)
489 mapping.remote_port = rand()%(0xffff - 1024) + 1024;
491 mapping.remote_port = port->external_port;
492 mapping.remote_host = "";
493 snprintf(description, 128, "%s %s at %s:%d",
495 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP": "UDP",
496 port->local_addr, port->local_port);
497 mapping.description = description;
498 mapping.protocol = port->protocol;
501 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to add port binding", port);
502 ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
505 port->state = LinphoneUpnpStateKo;
510 int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry) {
511 upnp_igd_port_mapping mapping;
514 if(lupnp->state != LinphoneUpnpStateOk) {
518 // Compute port binding state
519 if(port->state != LinphoneUpnpStateRemoving) {
520 port->to_add = FALSE;
521 switch(port->state) {
522 case LinphoneUpnpStateOk: {
524 port->state = LinphoneUpnpStateRemoving;
527 case LinphoneUpnpStateAdding: {
528 port->to_remove = TRUE;
537 // No retry if specified
538 if(port->retry != 0 && !retry) {
542 if(port->retry >= UPNP_REMOVE_MAX_RETRY) {
545 mapping.cookie = linphone_upnp_port_binding_retain(port);
546 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
548 mapping.remote_port = port->external_port;
549 mapping.remote_host = "";
550 mapping.protocol = port->protocol;
552 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to remove port binding", port);
553 ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
556 port->state = LinphoneUpnpStateKo;
562 * uPnP Core interfaces
565 int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool_t video) {
566 LinphoneCore *lc = call->core;
567 UpnpContext *lupnp = lc->upnp;
574 ms_mutex_lock(&lupnp->mutex);
576 // Don't handle when the call
577 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
583 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->audio->rtp,
584 UPNP_IGD_IP_PROTOCOL_UDP, (audio)? call->audio_port:0, UPNP_CALL_RETRY_DELAY);
586 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->audio->rtcp,
587 UPNP_IGD_IP_PROTOCOL_UDP, (audio)? call->audio_port+1:0, UPNP_CALL_RETRY_DELAY);
592 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->video->rtp,
593 UPNP_IGD_IP_PROTOCOL_UDP, (video)? call->video_port:0, UPNP_CALL_RETRY_DELAY);
595 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->video->rtcp,
596 UPNP_IGD_IP_PROTOCOL_UDP, (video)? call->video_port+1:0, UPNP_CALL_RETRY_DELAY);
599 ms_mutex_unlock(&lupnp->mutex);
602 * Update uPnP call state
604 linphone_upnp_call_process(call);
611 int linphone_core_update_upnp_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) {
612 bool_t audio = FALSE;
613 bool_t video = FALSE;
615 const SalStreamDescription *stream;
617 for (i = 0; i < md->n_total_streams; i++) {
618 stream = &md->streams[i];
619 if(stream->type == SalAudio) {
621 } else if(stream->type == SalVideo) {
626 return linphone_core_update_upnp_audio_video(call, audio, video);
629 int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
630 return linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
633 void linphone_core_update_upnp_state_in_call_stats(LinphoneCall *call) {
634 call->stats[LINPHONE_CALL_STATS_AUDIO].upnp_state = call->upnp_session->audio->state;
635 call->stats[LINPHONE_CALL_STATS_VIDEO].upnp_state = call->upnp_session->video->state;
638 void linphone_upnp_update_stream_state(UpnpStream *stream) {
639 if((stream->rtp == NULL || stream->rtp->state == LinphoneUpnpStateOk || stream->rtp->state == LinphoneUpnpStateIdle) &&
640 (stream->rtcp == NULL || stream->rtcp->state == LinphoneUpnpStateOk || stream->rtcp->state == LinphoneUpnpStateIdle)) {
641 stream->state = LinphoneUpnpStateOk;
642 } else if((stream->rtp != NULL &&
643 (stream->rtp->state == LinphoneUpnpStateAdding || stream->rtp->state == LinphoneUpnpStateRemoving)) ||
644 (stream->rtcp != NULL &&
645 (stream->rtcp->state == LinphoneUpnpStateAdding || stream->rtcp->state == LinphoneUpnpStateRemoving))) {
646 stream->state = LinphoneUpnpStatePending;
647 } else if((stream->rtp != NULL && stream->rtp->state == LinphoneUpnpStateKo) ||
648 (stream->rtcp != NULL && stream->rtcp->state == LinphoneUpnpStateKo)) {
649 stream->state = LinphoneUpnpStateKo;
651 ms_error("Invalid stream %p state", stream);
655 int linphone_upnp_call_process(LinphoneCall *call) {
656 LinphoneCore *lc = call->core;
657 UpnpContext *lupnp = lc->upnp;
659 LinphoneUpnpState oldState = 0, newState = 0;
665 ms_mutex_lock(&lupnp->mutex);
667 // Don't handle when the call
668 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
674 linphone_upnp_update_stream_state(call->upnp_session->audio);
679 linphone_upnp_update_stream_state(call->upnp_session->video);
684 linphone_core_update_upnp_state_in_call_stats(call);
687 * Update session state
689 oldState = call->upnp_session->state;
690 if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
691 call->upnp_session->video->state == LinphoneUpnpStateOk) {
692 call->upnp_session->state = LinphoneUpnpStateOk;
693 } else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
694 call->upnp_session->video->state == LinphoneUpnpStatePending) {
695 call->upnp_session->state = LinphoneUpnpStatePending;
696 } else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
697 call->upnp_session->video->state == LinphoneUpnpStateKo) {
698 call->upnp_session->state = LinphoneUpnpStateKo;
700 call->upnp_session->state = LinphoneUpnpStateIdle;
702 newState = call->upnp_session->state;
705 ms_mutex_unlock(&lupnp->mutex);
707 /* When change is done proceed update */
708 if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
709 (newState == LinphoneUpnpStateOk || newState == LinphoneUpnpStateKo)) {
710 if(call->upnp_session->state == LinphoneUpnpStateOk)
711 ms_message("uPnP IGD: uPnP for Call %p is ok", call);
713 ms_message("uPnP IGD: uPnP for Call %p is ko", call);
715 switch (call->state) {
716 case LinphoneCallUpdating:
717 linphone_core_start_update_call(lc, call);
719 case LinphoneCallUpdatedByRemote:
720 linphone_core_start_accept_call_update(lc, call);
722 case LinphoneCallOutgoingInit:
723 linphone_core_proceed_with_invite_if_ready(lc, call, NULL);
725 case LinphoneCallIdle:
726 linphone_core_notify_incoming_call(lc, call);
736 void linphone_core_upnp_refresh(UpnpContext *lupnp) {
737 MSList *global_list = NULL;
741 UpnpPortBinding *port_mapping, *port_mapping2;
743 ms_message("uPnP IGD: Refresh mappings");
745 if(lupnp->sip_udp != NULL) {
746 global_list = ms_list_append(global_list, lupnp->sip_udp);
748 if(lupnp->sip_tcp != NULL) {
749 global_list = ms_list_append(global_list, lupnp->sip_tcp);
751 if(lupnp->sip_tls != NULL) {
752 global_list = ms_list_append(global_list, lupnp->sip_tls);
755 list = lupnp->lc->calls;
756 while(list != NULL) {
757 call = (LinphoneCall *)list->data;
758 if(call->upnp_session != NULL) {
759 if(call->upnp_session->audio->rtp != NULL) {
760 global_list = ms_list_append(global_list, call->upnp_session->audio->rtp);
762 if(call->upnp_session->audio->rtcp != NULL) {
763 global_list = ms_list_append(global_list, call->upnp_session->audio->rtcp);
765 if(call->upnp_session->video->rtp != NULL) {
766 global_list = ms_list_append(global_list, call->upnp_session->video->rtp);
768 if(call->upnp_session->video->rtcp != NULL) {
769 global_list = ms_list_append(global_list, call->upnp_session->video->rtcp);
775 list = linphone_upnp_config_list_port_bindings(lupnp->lc->config);
776 for(item = list;item != NULL; item = item->next) {
777 port_mapping = (UpnpPortBinding *)item->data;
778 port_mapping2 = linphone_upnp_port_binding_equivalent_in_list(global_list, port_mapping);
779 if(port_mapping2 == NULL) {
780 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE);
781 } else if(port_mapping2->state == LinphoneUpnpStateIdle){
782 /* Force to remove */
783 port_mapping2->state = LinphoneUpnpStateOk;
786 ms_list_for_each(list, (void (*)(void*))linphone_upnp_port_binding_release);
787 list = ms_list_free(list);
790 // (Re)Add removed port bindings
792 while(list != NULL) {
793 port_mapping = (UpnpPortBinding *)list->data;
794 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE);
795 linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, TRUE);
798 global_list = ms_list_free(global_list);
801 void linphone_upnp_update_port_binding(UpnpContext *lupnp, UpnpPortBinding **port_mapping, upnp_igd_ip_protocol protocol, int port, int retry_delay) {
802 const char *local_addr, *external_addr;
803 time_t now = time(NULL);
805 if(*port_mapping != NULL) {
806 if(port != (*port_mapping)->local_port) {
807 linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
808 *port_mapping = NULL;
811 if(*port_mapping == NULL) {
812 *port_mapping = linphone_upnp_port_binding_new_or_collect(lupnp->pending_bindings, protocol, port, port);
816 local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
817 external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
819 // Force binding update on local address change
820 if(local_addr != NULL) {
821 if(strncmp((*port_mapping)->local_addr, local_addr, sizeof((*port_mapping)->local_addr))) {
822 linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
823 strncpy((*port_mapping)->local_addr, local_addr, sizeof((*port_mapping)->local_addr));
826 if(external_addr != NULL) {
827 strncpy((*port_mapping)->external_addr, external_addr, sizeof((*port_mapping)->external_addr));
830 // Add (if not already done) the binding
831 if(now - (*port_mapping)->last_update >= retry_delay) {
832 (*port_mapping)->last_update = now;
833 linphone_upnp_context_send_add_port_binding(lupnp, *port_mapping, FALSE);
836 if(*port_mapping != NULL) {
837 linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
838 *port_mapping = NULL;
843 void linphone_upnp_update_config(UpnpContext* lupnp) {
846 UpnpPortBinding *port_mapping;
849 for(item = lupnp->adding_configs;item!=NULL;item=item->next) {
850 port_mapping = (UpnpPortBinding *)item->data;
851 snprintf(key, sizeof(key), "%s-%d-%d",
852 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
853 port_mapping->external_port,
854 port_mapping->local_port);
855 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, "uPnP");
856 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping);
858 ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
859 lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
862 for(item = lupnp->removing_configs;item!=NULL;item=item->next) {
863 port_mapping = (UpnpPortBinding *)item->data;
864 snprintf(key, sizeof(key), "%s-%d-%d",
865 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
866 port_mapping->external_port,
867 port_mapping->local_port);
868 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, NULL);
869 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping);
871 ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
872 lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
875 void linphone_upnp_update_proxy(UpnpContext* lupnp, bool_t force) {
876 LinphoneUpnpState ready_state;
878 time_t now = (force)? (lupnp->last_ready_check + UPNP_CORE_READY_CHECK) : time(NULL);
880 /* Refresh registers if we are ready */
881 if(now - lupnp->last_ready_check >= UPNP_CORE_READY_CHECK) {
882 lupnp->last_ready_check = now;
883 ready_state = (_linphone_upnp_context_is_ready_for_register(lupnp))? LinphoneUpnpStateOk: LinphoneUpnpStateKo;
884 if(ready_state != lupnp->last_ready_state) {
885 for(item=linphone_core_get_proxy_config_list(lupnp->lc);item!=NULL;item=item->next) {
886 LinphoneProxyConfig *cfg=(LinphoneProxyConfig*)item->data;
887 if (linphone_proxy_config_register_enabled(cfg)) {
888 if (ready_state != LinphoneUpnpStateOk) {
889 // Only reset ithe registration if we require that upnp should be ok
890 if(lupnp->lc->sip_conf.register_only_when_upnp_is_ok) {
891 linphone_proxy_config_set_state(cfg, LinphoneRegistrationNone, "Registration impossible (uPnP not ready)");
900 lupnp->last_ready_state = ready_state;
905 bool_t linphone_core_upnp_hook(void *data) {
906 LCSipTransports transport;
907 UpnpContext *lupnp = (UpnpContext *)data;
909 ms_mutex_lock(&lupnp->mutex);
912 if(lupnp->state == LinphoneUpnpStateOk) {
913 linphone_core_get_sip_transports(lupnp->lc, &transport);
914 linphone_upnp_update_port_binding(lupnp, &lupnp->sip_udp, UPNP_IGD_IP_PROTOCOL_UDP, transport.udp_port, UPNP_CORE_RETRY_DELAY);
915 linphone_upnp_update_port_binding(lupnp, &lupnp->sip_tcp, UPNP_IGD_IP_PROTOCOL_TCP, transport.tcp_port, UPNP_CORE_RETRY_DELAY);
916 linphone_upnp_update_port_binding(lupnp, &lupnp->sip_tls, UPNP_IGD_IP_PROTOCOL_TCP, transport.tls_port, UPNP_CORE_RETRY_DELAY);
919 linphone_upnp_update_proxy(lupnp, FALSE);
920 linphone_upnp_update_config(lupnp);
922 ms_mutex_unlock(&lupnp->mutex);
926 int linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
928 SalStreamDescription *stream;
929 UpnpStream *upnpStream;
931 for (i = 0; i < desc->n_active_streams; i++) {
932 stream = &desc->streams[i];
934 if(stream->type == SalAudio) {
935 upnpStream = session->audio;
936 } else if(stream->type == SalVideo) {
937 upnpStream = session->video;
939 if(upnpStream != NULL) {
940 if(upnpStream->rtp != NULL && upnpStream->rtp->state == LinphoneUpnpStateOk) {
941 strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
942 stream->rtp_port = upnpStream->rtp->external_port;
944 if(upnpStream->rtcp != NULL && upnpStream->rtcp->state == LinphoneUpnpStateOk) {
945 strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
946 stream->rtcp_port = upnpStream->rtcp->external_port;
958 UpnpPortBinding *linphone_upnp_port_binding_new() {
959 UpnpPortBinding *port = NULL;
960 port = ms_new0(UpnpPortBinding,1);
961 ms_mutex_init(&port->mutex, NULL);
962 port->state = LinphoneUpnpStateIdle;
963 port->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
964 port->local_addr[0] = '\0';
965 port->local_port = -1;
966 port->external_addr[0] = '\0';
967 port->external_port = -1;
968 port->to_remove = FALSE;
969 port->to_add = FALSE;
971 port->last_update = 0;
975 UpnpPortBinding *linphone_upnp_port_binding_new_with_parameters(upnp_igd_ip_protocol protocol, int local_port, int external_port) {
976 UpnpPortBinding *port_binding = linphone_upnp_port_binding_new();
977 port_binding->protocol = protocol;
978 port_binding->local_port = local_port;
979 port_binding->external_port = external_port;
983 UpnpPortBinding *linphone_upnp_port_binding_new_or_collect(MSList *list, upnp_igd_ip_protocol protocol, int local_port, int external_port) {
984 UpnpPortBinding *tmp_binding;
985 UpnpPortBinding *end_binding;
986 end_binding = linphone_upnp_port_binding_new_with_parameters(protocol, local_port, external_port);
987 tmp_binding = linphone_upnp_port_binding_equivalent_in_list(list, end_binding);
988 if(tmp_binding != NULL) {
989 linphone_upnp_port_binding_release(end_binding);
990 end_binding = tmp_binding;
995 UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port) {
996 UpnpPortBinding *new_port = NULL;
997 new_port = ms_new0(UpnpPortBinding,1);
998 memcpy(new_port, port, sizeof(UpnpPortBinding));
999 ms_mutex_init(&new_port->mutex, NULL);
1004 void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port) {
1005 if(strlen(port->local_addr)) {
1006 ortp_log(level, "uPnP IGD: %s %s|%d->%s:%d (retry %d)", msg,
1007 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
1008 port->external_port,
1013 ortp_log(level, "uPnP IGD: %s %s|%d->%d (retry %d)", msg,
1014 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
1015 port->external_port,
1021 bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
1022 return port1->protocol == port2->protocol &&
1023 port1->local_port == port2->local_port &&
1024 port1->external_port == port2->external_port;
1027 UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port) {
1028 UpnpPortBinding *port_mapping;
1029 while(list != NULL) {
1030 port_mapping = (UpnpPortBinding *)list->data;
1031 if(linphone_upnp_port_binding_equal(port, port_mapping)) {
1032 return port_mapping;
1040 UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port) {
1041 ms_mutex_lock(&port->mutex);
1043 ms_mutex_unlock(&port->mutex);
1047 void linphone_upnp_port_binding_release(UpnpPortBinding *port) {
1048 ms_mutex_lock(&port->mutex);
1049 if(--port->ref == 0) {
1050 ms_mutex_unlock(&port->mutex);
1051 ms_mutex_destroy(&port->mutex);
1055 ms_mutex_unlock(&port->mutex);
1063 UpnpStream* linphone_upnp_stream_new() {
1064 UpnpStream *stream = ms_new0(UpnpStream,1);
1065 stream->state = LinphoneUpnpStateIdle;
1067 stream->rtcp = NULL;
1071 void linphone_upnp_stream_destroy(UpnpStream* stream) {
1072 if(stream->rtp != NULL) {
1073 linphone_upnp_port_binding_release(stream->rtp);
1076 if(stream->rtcp != NULL) {
1077 linphone_upnp_port_binding_release(stream->rtcp);
1078 stream->rtcp = NULL;
1088 UpnpSession* linphone_upnp_session_new(LinphoneCall* call) {
1089 UpnpSession *session = ms_new0(UpnpSession,1);
1090 session->call = call;
1091 session->state = LinphoneUpnpStateIdle;
1092 session->audio = linphone_upnp_stream_new();
1093 session->video = linphone_upnp_stream_new();
1097 void linphone_upnp_session_destroy(UpnpSession *session) {
1098 LinphoneCore *lc = session->call->core;
1100 if(lc->upnp != NULL) {
1101 /* Remove bindings */
1102 if(session->audio->rtp != NULL) {
1103 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtp, TRUE);
1105 if(session->audio->rtcp != NULL) {
1106 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtcp, TRUE);
1108 if(session->video->rtp != NULL) {
1109 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtp, TRUE);
1111 if(session->video->rtcp != NULL) {
1112 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtcp, TRUE);
1116 linphone_upnp_stream_destroy(session->audio);
1117 linphone_upnp_stream_destroy(session->video);
1121 LinphoneUpnpState linphone_upnp_session_get_state(UpnpSession *session) {
1122 return session->state;
1130 struct linphone_upnp_config_list_port_bindings_struct {
1131 struct _LpConfig *lpc;
1135 static void linphone_upnp_config_list_port_bindings_cb(const char *entry, struct linphone_upnp_config_list_port_bindings_struct *cookie) {
1136 char protocol_str[4]; // TCP or UDP
1137 upnp_igd_ip_protocol protocol;
1140 bool_t valid = TRUE;
1141 UpnpPortBinding *port;
1142 if(sscanf(entry, "%3s-%i-%i", protocol_str, &external_port, &local_port) == 3) {
1143 if(strcasecmp(protocol_str, "TCP") == 0) {
1144 protocol = UPNP_IGD_IP_PROTOCOL_TCP;
1145 } else if(strcasecmp(protocol_str, "UDP") == 0) {
1146 protocol = UPNP_IGD_IP_PROTOCOL_UDP;
1151 port = linphone_upnp_port_binding_new();
1152 port->state = LinphoneUpnpStateOk;
1153 port->protocol = protocol;
1154 port->external_port = external_port;
1155 port->local_port = local_port;
1156 cookie->retList = ms_list_append(cookie->retList, port);
1162 ms_warning("uPnP configuration invalid line: %s", entry);
1166 MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc) {
1167 struct linphone_upnp_config_list_port_bindings_struct cookie = {lpc, NULL};
1168 lp_config_for_each_entry(lpc, UPNP_SECTION_NAME, (void(*)(const char *, void*))linphone_upnp_config_list_port_bindings_cb, &cookie);
1169 return cookie.retList;
1172 void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1174 UpnpPortBinding *list_port;
1176 list = lupnp->removing_configs;
1177 while(list != NULL) {
1178 list_port = (UpnpPortBinding *)list->data;
1179 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1180 lupnp->removing_configs = ms_list_remove(lupnp->removing_configs, list_port);
1181 linphone_upnp_port_binding_release(list_port);
1184 list = ms_list_next(list);
1187 list = lupnp->adding_configs;
1188 while(list != NULL) {
1189 list_port = (UpnpPortBinding *)list->data;
1190 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1193 list = ms_list_next(list);
1196 list_port = linphone_upnp_port_binding_copy(port);
1197 lupnp->adding_configs = ms_list_append(lupnp->adding_configs, list_port);
1200 void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1202 UpnpPortBinding *list_port;
1204 list = lupnp->adding_configs;
1205 while(list != NULL) {
1206 list_port = (UpnpPortBinding *)list->data;
1207 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1208 lupnp->adding_configs = ms_list_remove(lupnp->adding_configs, list_port);
1209 linphone_upnp_port_binding_release(list_port);
1212 list = ms_list_next(list);
1215 list = lupnp->removing_configs;
1216 while(list != NULL) {
1217 list_port = (UpnpPortBinding *)list->data;
1218 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1221 list = ms_list_next(list);
1224 list_port = linphone_upnp_port_binding_copy(port);
1225 lupnp->removing_configs = ms_list_append(lupnp->removing_configs, list_port);