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);
97 MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc);
98 void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port);
99 void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port);
102 int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry);
103 int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry);
110 /* Convert uPnP IGD logs to ortp logs */
111 void linphone_upnp_igd_print(void *cookie, upnp_igd_print_level level, const char *fmt, va_list list) {
112 int ortp_level = ORTP_DEBUG;
114 case UPNP_IGD_MESSAGE:
115 ortp_level = ORTP_MESSAGE;
117 case UPNP_IGD_WARNING:
118 ortp_level = ORTP_DEBUG; // Too verbose otherwise
121 ortp_level = ORTP_DEBUG; // Too verbose otherwise
126 ortp_logv(ortp_level, fmt, list);
129 void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) {
130 UpnpContext *lupnp = (UpnpContext *)cookie;
131 upnp_igd_port_mapping *mapping = NULL;
132 UpnpPortBinding *port_mapping = NULL;
133 const char *ip_address = NULL;
134 const char *connection_status = NULL;
135 bool_t nat_enabled = FALSE;
136 LinphoneUpnpState old_state;
138 if(lupnp == NULL || lupnp->upnp_igd_ctxt == NULL) {
139 ms_error("uPnP IGD: Invalid context in callback");
143 ms_mutex_lock(&lupnp->mutex);
144 old_state = lupnp->state;
147 case UPNP_IGD_DEVICE_ADDED:
148 case UPNP_IGD_DEVICE_REMOVED:
149 case UPNP_IGD_EXTERNAL_IPADDRESS_CHANGED:
150 case UPNP_IGD_NAT_ENABLED_CHANGED:
151 case UPNP_IGD_CONNECTION_STATUS_CHANGED:
152 ip_address = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
153 connection_status = upnp_igd_get_connection_status(lupnp->upnp_igd_ctxt);
154 nat_enabled = upnp_igd_get_nat_enabled(lupnp->upnp_igd_ctxt);
156 if(ip_address == NULL || connection_status == NULL) {
157 ms_message("uPnP IGD: Pending");
158 lupnp->state = LinphoneUpnpStatePending;
159 } else if(strcasecmp(connection_status, "Connected") || !nat_enabled) {
160 ms_message("uPnP IGD: Not Available");
161 lupnp->state = LinphoneUpnpStateNotAvailable;
163 ms_message("uPnP IGD: Connected");
164 lupnp->state = LinphoneUpnpStateOk;
165 if(old_state != LinphoneUpnpStateOk) {
166 linphone_core_upnp_refresh(lupnp);
172 case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS:
173 mapping = (upnp_igd_port_mapping *) arg;
174 port_mapping = (UpnpPortBinding*) mapping->cookie;
175 port_mapping->external_port = mapping->remote_port;
176 port_mapping->state = LinphoneUpnpStateOk;
177 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Added port binding", port_mapping);
178 linphone_upnp_config_add_port_binding(lupnp, port_mapping);
182 case UPNP_IGD_PORT_MAPPING_ADD_FAILURE:
183 mapping = (upnp_igd_port_mapping *) arg;
184 port_mapping = (UpnpPortBinding*) mapping->cookie;
185 port_mapping->external_port = -1; //Force random external port
186 if(linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, TRUE) != 0) {
187 linphone_upnp_port_binding_log(ORTP_ERROR, "Can't add port binding", port_mapping);
192 case UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS:
193 mapping = (upnp_igd_port_mapping *) arg;
194 port_mapping = (UpnpPortBinding*) mapping->cookie;
195 port_mapping->state = LinphoneUpnpStateIdle;
196 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Removed port binding", port_mapping);
197 linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
201 case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE:
202 mapping = (upnp_igd_port_mapping *) arg;
203 port_mapping = (UpnpPortBinding*) mapping->cookie;
204 if(linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE) != 0) {
205 linphone_upnp_port_binding_log(ORTP_ERROR, "Can't remove port binding", port_mapping);
206 linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
215 if(port_mapping != NULL) {
217 * Execute delayed actions
219 if(port_mapping->to_remove) {
220 if(port_mapping->state == LinphoneUpnpStateOk) {
221 port_mapping->to_remove = FALSE;
222 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, FALSE);
223 } else if(port_mapping->state == LinphoneUpnpStateKo) {
224 port_mapping->to_remove = FALSE;
227 if(port_mapping->to_add) {
228 if(port_mapping->state == LinphoneUpnpStateIdle || port_mapping->state == LinphoneUpnpStateKo) {
229 port_mapping->to_add = FALSE;
230 linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, FALSE);
234 lupnp->pending_bindings = ms_list_remove(lupnp->pending_bindings, port_mapping);
235 linphone_upnp_port_binding_release(port_mapping);
239 * If there is no pending binding emit a signal
241 if(lupnp->pending_bindings == NULL) {
242 pthread_cond_signal(&lupnp->empty_cond);
244 ms_mutex_unlock(&lupnp->mutex);
252 UpnpContext* linphone_upnp_context_new(LinphoneCore *lc) {
253 UpnpContext *lupnp = (UpnpContext *)ms_new0(UpnpContext,1);
255 ms_mutex_init(&lupnp->mutex, NULL);
256 ms_cond_init(&lupnp->empty_cond, NULL);
258 lupnp->last_ready_check = 0;
259 lupnp->last_ready_state = LinphoneUpnpStateIdle;
262 lupnp->pending_bindings = NULL;
263 lupnp->adding_configs = NULL;
264 lupnp->removing_configs = NULL;
265 lupnp->state = LinphoneUpnpStateIdle;
266 ms_message("uPnP IGD: New %p for core %p", lupnp, lc);
269 lupnp->sip_udp = NULL;
270 lupnp->sip_tcp = NULL;
271 lupnp->sip_tls = NULL;
273 linphone_core_add_iterate_hook(lc, linphone_core_upnp_hook, lupnp);
275 lupnp->upnp_igd_ctxt = NULL;
276 lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, lupnp);
277 if(lupnp->upnp_igd_ctxt == NULL) {
278 lupnp->state = LinphoneUpnpStateKo;
279 ms_error("Can't create uPnP IGD context");
283 lupnp->state = LinphoneUpnpStatePending;
284 upnp_igd_start(lupnp->upnp_igd_ctxt);
289 void linphone_upnp_context_destroy(UpnpContext *lupnp) {
290 linphone_core_remove_iterate_hook(lupnp->lc, linphone_core_upnp_hook, lupnp);
292 ms_mutex_lock(&lupnp->mutex);
294 /* Send port binding removes */
295 if(lupnp->sip_udp != NULL) {
296 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_udp, TRUE);
298 if(lupnp->sip_tcp != NULL) {
299 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tcp, TRUE);
301 if(lupnp->sip_tls != NULL) {
302 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tls, TRUE);
305 /* Wait all pending bindings are done */
306 if(lupnp->pending_bindings != NULL) {
307 ms_message("uPnP IGD: Wait all pending port bindings ...");
308 ms_cond_wait(&lupnp->empty_cond, &lupnp->mutex);
310 ms_mutex_unlock(&lupnp->mutex);
312 if(lupnp->upnp_igd_ctxt != NULL) {
313 upnp_igd_destroy(lupnp->upnp_igd_ctxt);
316 /* Run one time the hook for configuration update */
317 linphone_core_upnp_hook(lupnp);
319 /* Release port bindings */
320 if(lupnp->sip_udp != NULL) {
321 linphone_upnp_port_binding_release(lupnp->sip_udp);
322 lupnp->sip_udp = NULL;
324 if(lupnp->sip_tcp != NULL) {
325 linphone_upnp_port_binding_release(lupnp->sip_tcp);
326 lupnp->sip_tcp = NULL;
328 if(lupnp->sip_tls != NULL) {
329 linphone_upnp_port_binding_release(lupnp->sip_tls);
330 lupnp->sip_tcp = NULL;
334 ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
335 lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
336 ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
337 lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
338 ms_list_for_each(lupnp->pending_bindings,(void (*)(void*))linphone_upnp_port_binding_release);
339 lupnp->pending_bindings = ms_list_free(lupnp->pending_bindings);
341 ms_mutex_destroy(&lupnp->mutex);
342 ms_cond_destroy(&lupnp->empty_cond);
344 ms_message("uPnP IGD: destroy %p", lupnp);
348 LinphoneUpnpState linphone_upnp_context_get_state(UpnpContext *lupnp) {
349 LinphoneUpnpState state;
350 ms_mutex_lock(&lupnp->mutex);
351 state = lupnp->state;
352 ms_mutex_unlock(&lupnp->mutex);
356 bool_t _linphone_upnp_context_is_ready_for_register(UpnpContext *lupnp) {
359 // 1 Check global uPnP state
360 ready = (lupnp->state == LinphoneUpnpStateOk);
362 // 2 Check external ip address
364 if (upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt) == NULL) {
369 // 3 Check sip ports bindings
371 if(lupnp->sip_udp != NULL) {
372 if(lupnp->sip_udp->state != LinphoneUpnpStateOk) {
375 } else if(lupnp->sip_tcp != NULL) {
376 if(lupnp->sip_tcp->state != LinphoneUpnpStateOk) {
379 } else if(lupnp->sip_tls != NULL) {
380 if(lupnp->sip_tls->state != LinphoneUpnpStateOk) {
391 bool_t linphone_upnp_context_is_ready_for_register(UpnpContext *lupnp) {
393 ms_mutex_lock(&lupnp->mutex);
394 ready = _linphone_upnp_context_is_ready_for_register(lupnp);
395 ms_mutex_unlock(&lupnp->mutex);
399 int linphone_upnp_context_get_external_port(UpnpContext *lupnp) {
401 ms_mutex_lock(&lupnp->mutex);
403 if(lupnp->sip_udp != NULL) {
404 if(lupnp->sip_udp->state == LinphoneUpnpStateOk) {
405 port = lupnp->sip_udp->external_port;
407 } else if(lupnp->sip_tcp != NULL) {
408 if(lupnp->sip_tcp->state == LinphoneUpnpStateOk) {
409 port = lupnp->sip_tcp->external_port;
411 } else if(lupnp->sip_tls != NULL) {
412 if(lupnp->sip_tls->state == LinphoneUpnpStateOk) {
413 port = lupnp->sip_tls->external_port;
417 ms_mutex_unlock(&lupnp->mutex);
421 const char* linphone_upnp_context_get_external_ipaddress(UpnpContext *lupnp) {
422 const char* addr = NULL;
423 ms_mutex_lock(&lupnp->mutex);
424 addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
425 ms_mutex_unlock(&lupnp->mutex);
429 int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry) {
430 upnp_igd_port_mapping mapping;
431 char description[128];
434 if(lupnp->state != LinphoneUpnpStateOk) {
438 // Compute port binding state
439 if(port->state != LinphoneUpnpStateAdding) {
440 port->to_remove = FALSE;
441 switch(port->state) {
442 case LinphoneUpnpStateKo:
443 case LinphoneUpnpStateIdle: {
445 port->state = LinphoneUpnpStateAdding;
448 case LinphoneUpnpStateRemoving: {
458 // No retry if specified
459 if(port->retry != 0 && !retry) {
463 if(port->retry >= UPNP_ADD_MAX_RETRY) {
466 mapping.cookie = linphone_upnp_port_binding_retain(port);
467 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
469 mapping.local_port = port->local_port;
470 mapping.local_host = port->local_addr;
471 if(port->external_port == -1)
472 mapping.remote_port = rand()%(0xffff - 1024) + 1024;
474 mapping.remote_port = port->external_port;
475 mapping.remote_host = "";
476 snprintf(description, 128, "%s %s at %s:%d",
478 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP": "UDP",
479 port->local_addr, port->local_port);
480 mapping.description = description;
481 mapping.protocol = port->protocol;
484 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to add port binding", port);
485 ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
488 port->state = LinphoneUpnpStateKo;
493 int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry) {
494 upnp_igd_port_mapping mapping;
497 if(lupnp->state != LinphoneUpnpStateOk) {
501 // Compute port binding state
502 if(port->state != LinphoneUpnpStateRemoving) {
503 port->to_add = FALSE;
504 switch(port->state) {
505 case LinphoneUpnpStateOk: {
507 port->state = LinphoneUpnpStateRemoving;
510 case LinphoneUpnpStateAdding: {
511 port->to_remove = TRUE;
520 // No retry if specified
521 if(port->retry != 0 && !retry) {
525 if(port->retry >= UPNP_REMOVE_MAX_RETRY) {
528 mapping.cookie = linphone_upnp_port_binding_retain(port);
529 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
531 mapping.remote_port = port->external_port;
532 mapping.remote_host = "";
533 mapping.protocol = port->protocol;
535 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to remove port binding", port);
536 ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
539 port->state = LinphoneUpnpStateKo;
545 * uPnP Core interfaces
548 int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool_t video) {
549 LinphoneCore *lc = call->core;
550 UpnpContext *lupnp = lc->upnp;
557 ms_mutex_lock(&lupnp->mutex);
559 // Don't handle when the call
560 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
566 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->audio->rtp,
567 UPNP_IGD_IP_PROTOCOL_UDP, (audio)? call->audio_port:0, UPNP_CALL_RETRY_DELAY);
569 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->audio->rtcp,
570 UPNP_IGD_IP_PROTOCOL_UDP, (audio)? call->audio_port+1:0, UPNP_CALL_RETRY_DELAY);
575 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->video->rtp,
576 UPNP_IGD_IP_PROTOCOL_UDP, (video)? call->video_port:0, UPNP_CALL_RETRY_DELAY);
578 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->video->rtcp,
579 UPNP_IGD_IP_PROTOCOL_UDP, (video)? call->video_port+1:0, UPNP_CALL_RETRY_DELAY);
582 ms_mutex_unlock(&lupnp->mutex);
585 * Update uPnP call state
587 linphone_upnp_call_process(call);
594 int linphone_core_update_upnp_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) {
595 bool_t audio = FALSE;
596 bool_t video = FALSE;
598 const SalStreamDescription *stream;
600 for (i = 0; i < md->n_total_streams; i++) {
601 stream = &md->streams[i];
602 if(stream->type == SalAudio) {
604 } else if(stream->type == SalVideo) {
609 return linphone_core_update_upnp_audio_video(call, audio, video);
612 int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
613 return linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
616 void linphone_core_update_upnp_state_in_call_stats(LinphoneCall *call) {
617 call->stats[LINPHONE_CALL_STATS_AUDIO].upnp_state = call->upnp_session->audio->state;
618 call->stats[LINPHONE_CALL_STATS_VIDEO].upnp_state = call->upnp_session->video->state;
621 void linphone_upnp_update_stream_state(UpnpStream *stream) {
622 if((stream->rtp == NULL || stream->rtp->state == LinphoneUpnpStateOk || stream->rtp->state == LinphoneUpnpStateIdle) &&
623 (stream->rtcp == NULL || stream->rtcp->state == LinphoneUpnpStateOk || stream->rtcp->state == LinphoneUpnpStateIdle)) {
624 stream->state = LinphoneUpnpStateOk;
625 } else if((stream->rtp != NULL &&
626 (stream->rtp->state == LinphoneUpnpStateAdding || stream->rtp->state == LinphoneUpnpStateRemoving)) ||
627 (stream->rtcp != NULL &&
628 (stream->rtcp->state == LinphoneUpnpStateAdding || stream->rtcp->state == LinphoneUpnpStateRemoving))) {
629 stream->state = LinphoneUpnpStatePending;
630 } else if((stream->rtp != NULL && stream->rtp->state == LinphoneUpnpStateKo) ||
631 (stream->rtcp != NULL && stream->rtcp->state == LinphoneUpnpStateKo)) {
632 stream->state = LinphoneUpnpStateKo;
634 ms_error("Invalid stream %p state", stream);
638 int linphone_upnp_call_process(LinphoneCall *call) {
639 LinphoneCore *lc = call->core;
640 UpnpContext *lupnp = lc->upnp;
642 LinphoneUpnpState oldState = 0, newState = 0;
648 ms_mutex_lock(&lupnp->mutex);
650 // Don't handle when the call
651 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
657 linphone_upnp_update_stream_state(call->upnp_session->audio);
662 linphone_upnp_update_stream_state(call->upnp_session->video);
667 linphone_core_update_upnp_state_in_call_stats(call);
670 * Update session state
672 oldState = call->upnp_session->state;
673 if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
674 call->upnp_session->video->state == LinphoneUpnpStateOk) {
675 call->upnp_session->state = LinphoneUpnpStateOk;
676 } else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
677 call->upnp_session->video->state == LinphoneUpnpStatePending) {
678 call->upnp_session->state = LinphoneUpnpStatePending;
679 } else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
680 call->upnp_session->video->state == LinphoneUpnpStateKo) {
681 call->upnp_session->state = LinphoneUpnpStateKo;
683 call->upnp_session->state = LinphoneUpnpStateIdle;
685 newState = call->upnp_session->state;
688 ms_mutex_unlock(&lupnp->mutex);
690 /* When change is done proceed update */
691 if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
692 (newState == LinphoneUpnpStateOk || newState == LinphoneUpnpStateKo)) {
693 if(call->upnp_session->state == LinphoneUpnpStateOk)
694 ms_message("uPnP IGD: uPnP for Call %p is ok", call);
696 ms_message("uPnP IGD: uPnP for Call %p is ko", call);
698 switch (call->state) {
699 case LinphoneCallUpdating:
700 linphone_core_start_update_call(lc, call);
702 case LinphoneCallUpdatedByRemote:
703 linphone_core_start_accept_call_update(lc, call);
705 case LinphoneCallOutgoingInit:
706 linphone_core_proceed_with_invite_if_ready(lc, call, NULL);
708 case LinphoneCallIdle:
709 linphone_core_notify_incoming_call(lc, call);
719 void linphone_core_upnp_refresh(UpnpContext *lupnp) {
720 MSList *global_list = NULL;
724 UpnpPortBinding *port_mapping, *port_mapping2;
726 ms_message("uPnP IGD: Refresh mappings");
728 if(lupnp->sip_udp != NULL) {
729 global_list = ms_list_append(global_list, lupnp->sip_udp);
731 if(lupnp->sip_tcp != NULL) {
732 global_list = ms_list_append(global_list, lupnp->sip_tcp);
734 if(lupnp->sip_tls != NULL) {
735 global_list = ms_list_append(global_list, lupnp->sip_tls);
738 list = lupnp->lc->calls;
739 while(list != NULL) {
740 call = (LinphoneCall *)list->data;
741 if(call->upnp_session != NULL) {
742 if(call->upnp_session->audio->rtp != NULL) {
743 global_list = ms_list_append(global_list, call->upnp_session->audio->rtp);
745 if(call->upnp_session->audio->rtcp != NULL) {
746 global_list = ms_list_append(global_list, call->upnp_session->audio->rtcp);
748 if(call->upnp_session->video->rtp != NULL) {
749 global_list = ms_list_append(global_list, call->upnp_session->video->rtp);
751 if(call->upnp_session->video->rtcp != NULL) {
752 global_list = ms_list_append(global_list, call->upnp_session->video->rtcp);
758 list = linphone_upnp_config_list_port_bindings(lupnp->lc->config);
759 for(item = list;item != NULL; item = item->next) {
760 port_mapping = (UpnpPortBinding *)item->data;
761 port_mapping2 = linphone_upnp_port_binding_equivalent_in_list(global_list, port_mapping);
762 if(port_mapping2 == NULL) {
763 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE);
764 } else if(port_mapping2->state == LinphoneUpnpStateIdle){
765 /* Force to remove */
766 port_mapping2->state = LinphoneUpnpStateOk;
769 ms_list_for_each(list, (void (*)(void*))linphone_upnp_port_binding_release);
770 list = ms_list_free(list);
773 // (Re)Add removed port bindings
775 while(list != NULL) {
776 port_mapping = (UpnpPortBinding *)list->data;
777 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE);
778 linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, TRUE);
781 global_list = ms_list_free(global_list);
784 void linphone_upnp_update_port_binding(UpnpContext *lupnp, UpnpPortBinding **port_mapping, upnp_igd_ip_protocol protocol, int port, int retry_delay) {
785 const char *local_addr, *external_addr;
786 time_t now = time(NULL);
788 if(*port_mapping != NULL) {
789 if(port != (*port_mapping)->local_port) {
790 linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
791 *port_mapping = NULL;
794 if(*port_mapping == NULL) {
795 *port_mapping = linphone_upnp_port_binding_new_or_collect(lupnp->pending_bindings, protocol, port, port);
799 local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
800 external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
802 // Force binding update on local address change
803 if(local_addr != NULL) {
804 if(strncmp((*port_mapping)->local_addr, local_addr, sizeof((*port_mapping)->local_addr))) {
805 linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
806 strncpy((*port_mapping)->local_addr, local_addr, sizeof((*port_mapping)->local_addr));
809 if(external_addr != NULL) {
810 strncpy((*port_mapping)->external_addr, external_addr, sizeof((*port_mapping)->external_addr));
813 // Add (if not already done) the binding
814 if(now - (*port_mapping)->last_update >= retry_delay) {
815 (*port_mapping)->last_update = now;
816 linphone_upnp_context_send_add_port_binding(lupnp, *port_mapping, FALSE);
819 if(*port_mapping != NULL) {
820 linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
821 *port_mapping = NULL;
826 bool_t linphone_core_upnp_hook(void *data) {
828 LCSipTransports transport;
830 LinphoneUpnpState ready_state;
831 time_t now = time(NULL);
832 UpnpPortBinding *port_mapping;
833 UpnpContext *lupnp = (UpnpContext *)data;
835 ms_mutex_lock(&lupnp->mutex);
838 if(lupnp->state == LinphoneUpnpStateOk) {
839 linphone_core_get_sip_transports(lupnp->lc, &transport);
840 linphone_upnp_update_port_binding(lupnp, &lupnp->sip_udp, UPNP_IGD_IP_PROTOCOL_UDP, transport.udp_port, UPNP_CORE_RETRY_DELAY);
841 linphone_upnp_update_port_binding(lupnp, &lupnp->sip_tcp, UPNP_IGD_IP_PROTOCOL_TCP, transport.tcp_port, UPNP_CORE_RETRY_DELAY);
842 linphone_upnp_update_port_binding(lupnp, &lupnp->sip_tls, UPNP_IGD_IP_PROTOCOL_TCP, transport.tls_port, UPNP_CORE_RETRY_DELAY);
845 /* Refresh registers if we are ready */
846 if(now - lupnp->last_ready_check >= UPNP_CORE_READY_CHECK) {
847 lupnp->last_ready_check = now;
848 ready_state = (_linphone_upnp_context_is_ready_for_register(lupnp))? LinphoneUpnpStateOk: LinphoneUpnpStateKo;
849 if(ready_state != lupnp->last_ready_state) {
850 for(item=linphone_core_get_proxy_config_list(lupnp->lc);item!=NULL;item=item->next) {
851 LinphoneProxyConfig *cfg=(LinphoneProxyConfig*)item->data;
852 if (linphone_proxy_config_register_enabled(cfg)) {
853 if (ready_state != LinphoneUpnpStateOk) {
854 // Only reset ithe registration if we require that upnp should be ok
855 if(lupnp->lc->sip_conf.register_only_when_upnp_is_ok) {
856 linphone_proxy_config_set_state(cfg, LinphoneRegistrationNone, "Registration impossible (uPnP not ready)");
863 lupnp->last_ready_state = ready_state;
868 for(item = lupnp->adding_configs;item!=NULL;item=item->next) {
869 port_mapping = (UpnpPortBinding *)item->data;
870 snprintf(key, sizeof(key), "%s-%d-%d",
871 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
872 port_mapping->external_port,
873 port_mapping->local_port);
874 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, "uPnP");
875 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping);
877 ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
878 lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
881 for(item = lupnp->removing_configs;item!=NULL;item=item->next) {
882 port_mapping = (UpnpPortBinding *)item->data;
883 snprintf(key, sizeof(key), "%s-%d-%d",
884 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
885 port_mapping->external_port,
886 port_mapping->local_port);
887 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, NULL);
888 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping);
890 ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
891 lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
893 ms_mutex_unlock(&lupnp->mutex);
897 int linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
899 SalStreamDescription *stream;
900 UpnpStream *upnpStream;
902 for (i = 0; i < desc->n_active_streams; i++) {
903 stream = &desc->streams[i];
905 if(stream->type == SalAudio) {
906 upnpStream = session->audio;
907 } else if(stream->type == SalVideo) {
908 upnpStream = session->video;
910 if(upnpStream != NULL) {
911 if(upnpStream->rtp != NULL && upnpStream->rtp->state == LinphoneUpnpStateOk) {
912 strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
913 stream->rtp_port = upnpStream->rtp->external_port;
915 if(upnpStream->rtcp != NULL && upnpStream->rtcp->state == LinphoneUpnpStateOk) {
916 strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
917 stream->rtcp_port = upnpStream->rtcp->external_port;
929 UpnpPortBinding *linphone_upnp_port_binding_new() {
930 UpnpPortBinding *port = NULL;
931 port = ms_new0(UpnpPortBinding,1);
932 ms_mutex_init(&port->mutex, NULL);
933 port->state = LinphoneUpnpStateIdle;
934 port->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
935 port->local_addr[0] = '\0';
936 port->local_port = -1;
937 port->external_addr[0] = '\0';
938 port->external_port = -1;
939 port->to_remove = FALSE;
940 port->to_add = FALSE;
942 port->last_update = 0;
946 UpnpPortBinding *linphone_upnp_port_binding_new_with_parameters(upnp_igd_ip_protocol protocol, int local_port, int external_port) {
947 UpnpPortBinding *port_binding = linphone_upnp_port_binding_new();
948 port_binding->protocol = protocol;
949 port_binding->local_port = local_port;
950 port_binding->external_port = external_port;
954 UpnpPortBinding *linphone_upnp_port_binding_new_or_collect(MSList *list, upnp_igd_ip_protocol protocol, int local_port, int external_port) {
955 UpnpPortBinding *tmp_binding;
956 UpnpPortBinding *end_binding;
957 end_binding = linphone_upnp_port_binding_new_with_parameters(protocol, local_port, external_port);
958 tmp_binding = linphone_upnp_port_binding_equivalent_in_list(list, end_binding);
959 if(tmp_binding != NULL) {
960 linphone_upnp_port_binding_release(end_binding);
961 end_binding = tmp_binding;
966 UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port) {
967 UpnpPortBinding *new_port = NULL;
968 new_port = ms_new0(UpnpPortBinding,1);
969 memcpy(new_port, port, sizeof(UpnpPortBinding));
970 ms_mutex_init(&new_port->mutex, NULL);
975 void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port) {
976 if(strlen(port->local_addr)) {
977 ortp_log(level, "uPnP IGD: %s %s|%d->%s:%d (retry %d)", msg,
978 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
984 ortp_log(level, "uPnP IGD: %s %s|%d->%d (retry %d)", msg,
985 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
992 bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
993 return port1->protocol == port2->protocol &&
994 port1->local_port == port2->local_port &&
995 port1->external_port == port2->external_port;
998 UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port) {
999 UpnpPortBinding *port_mapping;
1000 while(list != NULL) {
1001 port_mapping = (UpnpPortBinding *)list->data;
1002 if(linphone_upnp_port_binding_equal(port, port_mapping)) {
1003 return port_mapping;
1011 UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port) {
1012 ms_mutex_lock(&port->mutex);
1014 ms_mutex_unlock(&port->mutex);
1018 void linphone_upnp_port_binding_release(UpnpPortBinding *port) {
1019 ms_mutex_lock(&port->mutex);
1020 if(--port->ref == 0) {
1021 ms_mutex_unlock(&port->mutex);
1022 ms_mutex_destroy(&port->mutex);
1026 ms_mutex_unlock(&port->mutex);
1034 UpnpStream* linphone_upnp_stream_new() {
1035 UpnpStream *stream = ms_new0(UpnpStream,1);
1036 stream->state = LinphoneUpnpStateIdle;
1038 stream->rtcp = NULL;
1042 void linphone_upnp_stream_destroy(UpnpStream* stream) {
1043 if(stream->rtp != NULL) {
1044 linphone_upnp_port_binding_release(stream->rtp);
1047 if(stream->rtcp != NULL) {
1048 linphone_upnp_port_binding_release(stream->rtcp);
1049 stream->rtcp = NULL;
1059 UpnpSession* linphone_upnp_session_new(LinphoneCall* call) {
1060 UpnpSession *session = ms_new0(UpnpSession,1);
1061 session->call = call;
1062 session->state = LinphoneUpnpStateIdle;
1063 session->audio = linphone_upnp_stream_new();
1064 session->video = linphone_upnp_stream_new();
1068 void linphone_upnp_session_destroy(UpnpSession *session) {
1069 LinphoneCore *lc = session->call->core;
1071 if(lc->upnp != NULL) {
1072 /* Remove bindings */
1073 if(session->audio->rtp != NULL) {
1074 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtp, TRUE);
1076 if(session->audio->rtcp != NULL) {
1077 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtcp, TRUE);
1079 if(session->video->rtp != NULL) {
1080 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtp, TRUE);
1082 if(session->video->rtcp != NULL) {
1083 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtcp, TRUE);
1087 linphone_upnp_stream_destroy(session->audio);
1088 linphone_upnp_stream_destroy(session->video);
1092 LinphoneUpnpState linphone_upnp_session_get_state(UpnpSession *session) {
1093 return session->state;
1101 struct linphone_upnp_config_list_port_bindings_struct {
1102 struct _LpConfig *lpc;
1106 static void linphone_upnp_config_list_port_bindings_cb(const char *entry, struct linphone_upnp_config_list_port_bindings_struct *cookie) {
1107 char protocol_str[4]; // TCP or UDP
1108 upnp_igd_ip_protocol protocol;
1111 bool_t valid = TRUE;
1112 UpnpPortBinding *port;
1113 if(sscanf(entry, "%3s-%i-%i", protocol_str, &external_port, &local_port) == 3) {
1114 if(strcasecmp(protocol_str, "TCP") == 0) {
1115 protocol = UPNP_IGD_IP_PROTOCOL_TCP;
1116 } else if(strcasecmp(protocol_str, "UDP") == 0) {
1117 protocol = UPNP_IGD_IP_PROTOCOL_UDP;
1122 port = linphone_upnp_port_binding_new();
1123 port->state = LinphoneUpnpStateOk;
1124 port->protocol = protocol;
1125 port->external_port = external_port;
1126 port->local_port = local_port;
1127 cookie->retList = ms_list_append(cookie->retList, port);
1133 ms_warning("uPnP configuration invalid line: %s", entry);
1137 MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc) {
1138 struct linphone_upnp_config_list_port_bindings_struct cookie = {lpc, NULL};
1139 lp_config_for_each_entry(lpc, UPNP_SECTION_NAME, (void(*)(const char *, void*))linphone_upnp_config_list_port_bindings_cb, &cookie);
1140 return cookie.retList;
1143 void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1145 UpnpPortBinding *list_port;
1147 list = lupnp->removing_configs;
1148 while(list != NULL) {
1149 list_port = (UpnpPortBinding *)list->data;
1150 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1151 lupnp->removing_configs = ms_list_remove(lupnp->removing_configs, list_port);
1152 linphone_upnp_port_binding_release(list_port);
1155 list = ms_list_next(list);
1158 list = lupnp->adding_configs;
1159 while(list != NULL) {
1160 list_port = (UpnpPortBinding *)list->data;
1161 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1164 list = ms_list_next(list);
1167 list_port = linphone_upnp_port_binding_copy(port);
1168 lupnp->adding_configs = ms_list_append(lupnp->adding_configs, list_port);
1171 void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1173 UpnpPortBinding *list_port;
1175 list = lupnp->adding_configs;
1176 while(list != NULL) {
1177 list_port = (UpnpPortBinding *)list->data;
1178 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1179 lupnp->adding_configs = ms_list_remove(lupnp->adding_configs, list_port);
1180 linphone_upnp_port_binding_release(list_port);
1183 list = ms_list_next(list);
1186 list = lupnp->removing_configs;
1187 while(list != NULL) {
1188 list_port = (UpnpPortBinding *)list->data;
1189 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1192 list = ms_list_next(list);
1195 list_port = linphone_upnp_port_binding_copy(port);
1196 lupnp->removing_configs = ms_list_append(lupnp->removing_configs, list_port);