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"
32 typedef struct _UpnpPortBinding {
34 LinphoneUpnpState state;
35 upnp_igd_ip_protocol protocol;
36 char local_addr[LINPHONE_IPADDR_SIZE];
38 char external_addr[LINPHONE_IPADDR_SIZE];
46 typedef struct _UpnpStream {
48 UpnpPortBinding *rtcp;
49 LinphoneUpnpState state;
56 LinphoneUpnpState state;
61 upnp_igd_context *upnp_igd_ctxt;
62 UpnpPortBinding *sip_tcp;
63 UpnpPortBinding *sip_tls;
64 UpnpPortBinding *sip_udp;
65 LinphoneUpnpState state;
66 MSList *removing_configs;
67 MSList *adding_configs;
68 MSList *pending_bindings;
76 bool_t linphone_core_upnp_hook(void *data);
77 void linphone_core_upnp_refresh(UpnpContext *ctx);
79 UpnpPortBinding *linphone_upnp_port_binding_new();
80 UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port);
81 bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2);
82 UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port);
83 UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port);
84 void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port);
85 void linphone_upnp_port_binding_release(UpnpPortBinding *port);
87 MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc);
88 void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port);
89 void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port);
91 int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port);
92 int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port);
99 /* Convert uPnP IGD logs to ortp logs */
100 void linphone_upnp_igd_print(void *cookie, upnp_igd_print_level level, const char *fmt, va_list list) {
101 int ortp_level = ORTP_DEBUG;
103 case UPNP_IGD_MESSAGE:
104 ortp_level = ORTP_MESSAGE;
106 case UPNP_IGD_WARNING:
107 ortp_level = ORTP_DEBUG; // Too verbose otherwise
110 ortp_level = ORTP_DEBUG; // Too verbose otherwise
115 ortp_logv(ortp_level, fmt, list);
118 void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) {
119 UpnpContext *lupnp = (UpnpContext *)cookie;
120 upnp_igd_port_mapping *mapping = NULL;
121 UpnpPortBinding *port_mapping = NULL;
122 const char *ip_address = NULL;
123 const char *connection_status = NULL;
124 bool_t nat_enabled = FALSE;
125 LinphoneUpnpState old_state;
127 if(lupnp == NULL || lupnp->upnp_igd_ctxt == NULL) {
128 ms_error("uPnP IGD: Invalid context in callback");
132 ms_mutex_lock(&lupnp->mutex);
133 old_state = lupnp->state;
136 case UPNP_IGD_EXTERNAL_IPADDRESS_CHANGED:
137 case UPNP_IGD_NAT_ENABLED_CHANGED:
138 case UPNP_IGD_CONNECTION_STATUS_CHANGED:
139 ip_address = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
140 connection_status = upnp_igd_get_connection_status(lupnp->upnp_igd_ctxt);
141 nat_enabled = upnp_igd_get_nat_enabled(lupnp->upnp_igd_ctxt);
143 if(ip_address == NULL || connection_status == NULL) {
144 ms_message("uPnP IGD: Pending");
145 lupnp->state = LinphoneUpnpStatePending;
146 } else if(strcasecmp(connection_status, "Connected") || !nat_enabled) {
147 ms_message("uPnP IGD: Not Available");
148 lupnp->state = LinphoneUpnpStateNotAvailable;
150 ms_message("uPnP IGD: Connected");
151 lupnp->state = LinphoneUpnpStateOk;
152 if(old_state != LinphoneUpnpStateOk) {
153 linphone_core_upnp_refresh(lupnp);
159 case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS:
160 mapping = (upnp_igd_port_mapping *) arg;
161 port_mapping = (UpnpPortBinding*) mapping->cookie;
162 port_mapping->external_port = mapping->remote_port;
163 port_mapping->state = LinphoneUpnpStateOk;
164 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Added port binding", port_mapping);
165 linphone_upnp_config_add_port_binding(lupnp, port_mapping);
169 case UPNP_IGD_PORT_MAPPING_ADD_FAILURE:
170 mapping = (upnp_igd_port_mapping *) arg;
171 port_mapping = (UpnpPortBinding*) mapping->cookie;
172 port_mapping->external_port = -1; //Force random external port
173 if(linphone_upnp_context_send_add_port_binding(lupnp, port_mapping) != 0) {
174 linphone_upnp_port_binding_log(ORTP_ERROR, "Can't add port binding", port_mapping);
179 case UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS:
180 mapping = (upnp_igd_port_mapping *) arg;
181 port_mapping = (UpnpPortBinding*) mapping->cookie;
182 port_mapping->state = LinphoneUpnpStateIdle;
183 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Removed port binding", port_mapping);
184 linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
188 case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE:
189 mapping = (upnp_igd_port_mapping *) arg;
190 port_mapping = (UpnpPortBinding*) mapping->cookie;
191 if(linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping) != 0) {
192 linphone_upnp_port_binding_log(ORTP_ERROR, "Can't remove port binding", port_mapping);
193 linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
202 if(port_mapping != NULL) {
204 * Execute delayed actions
206 if(port_mapping->to_remove) {
207 if(port_mapping->state == LinphoneUpnpStateOk) {
208 port_mapping->to_remove = FALSE;
209 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping);
210 } else if(port_mapping->state == LinphoneUpnpStateKo) {
211 port_mapping->to_remove = FALSE;
214 if(port_mapping->to_add) {
215 if(port_mapping->state == LinphoneUpnpStateIdle || port_mapping->state == LinphoneUpnpStateKo) {
216 port_mapping->to_add = FALSE;
217 linphone_upnp_context_send_add_port_binding(lupnp, port_mapping);
221 lupnp->pending_bindings = ms_list_remove(lupnp->pending_bindings, port_mapping);
222 linphone_upnp_port_binding_release(port_mapping);
226 * If there is no pending binding emit a signal
228 if(lupnp->pending_bindings == NULL) {
229 pthread_cond_signal(&lupnp->empty_cond);
231 ms_mutex_unlock(&lupnp->mutex);
239 UpnpContext* linphone_upnp_context_new(LinphoneCore *lc) {
240 LCSipTransports transport;
241 UpnpContext *lupnp = (UpnpContext *)ms_new0(UpnpContext,1);
242 const char *ip_address;
244 ms_mutex_init(&lupnp->mutex, NULL);
245 ms_cond_init(&lupnp->empty_cond, NULL);
248 lupnp->pending_bindings = NULL;
249 lupnp->adding_configs = NULL;
250 lupnp->removing_configs = NULL;
251 lupnp->state = LinphoneUpnpStateIdle;
252 ms_message("uPnP IGD: New %p for core %p", lupnp, lc);
254 linphone_core_get_sip_transports(lc, &transport);
255 if(transport.udp_port != 0) {
256 lupnp->sip_udp = linphone_upnp_port_binding_new();
257 lupnp->sip_udp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
258 lupnp->sip_udp->local_port = transport.udp_port;
259 lupnp->sip_udp->external_port = transport.udp_port;
261 lupnp->sip_udp = NULL;
263 if(transport.tcp_port != 0) {
264 lupnp->sip_tcp = linphone_upnp_port_binding_new();
265 lupnp->sip_tcp->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
266 lupnp->sip_tcp->local_port = transport.tcp_port;
267 lupnp->sip_tcp->external_port = transport.tcp_port;
269 lupnp->sip_tcp = NULL;
271 if(transport.tls_port != 0) {
272 lupnp->sip_tls = linphone_upnp_port_binding_new();
273 lupnp->sip_tls->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
274 lupnp->sip_tls->local_port = transport.tls_port;
275 lupnp->sip_tls->external_port = transport.tls_port;
277 lupnp->sip_tls = NULL;
280 linphone_core_add_iterate_hook(lc, linphone_core_upnp_hook, lupnp);
282 lupnp->upnp_igd_ctxt = NULL;
283 lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, lupnp);
284 if(lupnp->upnp_igd_ctxt == NULL) {
285 lupnp->state = LinphoneUpnpStateKo;
286 ms_error("Can't create uPnP IGD context");
290 ip_address = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
291 if(lupnp->sip_udp != NULL) {
292 strncpy(lupnp->sip_udp->local_addr, ip_address, sizeof(lupnp->sip_udp->local_addr));
294 if(lupnp->sip_tcp != NULL) {
295 strncpy(lupnp->sip_tcp->local_addr, ip_address, sizeof(lupnp->sip_tcp->local_addr));
297 if(lupnp->sip_tls != NULL) {
298 strncpy(lupnp->sip_tls->local_addr, ip_address, sizeof(lupnp->sip_tls->local_addr));
301 lupnp->state = LinphoneUpnpStatePending;
302 upnp_igd_start(lupnp->upnp_igd_ctxt);
307 void linphone_upnp_context_destroy(UpnpContext *lupnp) {
309 * Not need, all hooks are removed before
310 * linphone_core_remove_iterate_hook(lc, linphone_core_upnp_hook, lc);
313 ms_mutex_lock(&lupnp->mutex);
315 /* Send port binding removes */
316 if(lupnp->sip_udp != NULL) {
317 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_udp);
318 lupnp->sip_udp = NULL;
320 if(lupnp->sip_tcp != NULL) {
321 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tcp);
322 lupnp->sip_tcp = NULL;
324 if(lupnp->sip_tls != NULL) {
325 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tls);
326 lupnp->sip_tcp = NULL;
329 /* Wait all pending bindings are done */
330 if(lupnp->pending_bindings != NULL) {
331 ms_message("uPnP IGD: Wait all pending port bindings ...");
332 ms_cond_wait(&lupnp->empty_cond, &lupnp->mutex);
334 ms_mutex_unlock(&lupnp->mutex);
336 if(lupnp->upnp_igd_ctxt != NULL) {
337 upnp_igd_destroy(lupnp->upnp_igd_ctxt);
340 /* Run one time the hook for configuration update */
341 linphone_core_upnp_hook(lupnp);
343 /* Release port bindings */
344 if(lupnp->sip_udp != NULL) {
345 linphone_upnp_port_binding_release(lupnp->sip_udp);
346 lupnp->sip_udp = NULL;
348 if(lupnp->sip_tcp != NULL) {
349 linphone_upnp_port_binding_release(lupnp->sip_tcp);
350 lupnp->sip_tcp = NULL;
352 if(lupnp->sip_tls != NULL) {
353 linphone_upnp_port_binding_release(lupnp->sip_tls);
354 lupnp->sip_tcp = NULL;
358 ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
359 lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
360 ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
361 lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
362 ms_list_for_each(lupnp->pending_bindings,(void (*)(void*))linphone_upnp_port_binding_release);
363 lupnp->pending_bindings = ms_list_free(lupnp->pending_bindings);
365 ms_mutex_destroy(&lupnp->mutex);
366 ms_cond_destroy(&lupnp->empty_cond);
368 ms_message("uPnP IGD: destroy %p", lupnp);
372 LinphoneUpnpState linphone_upnp_context_get_state(UpnpContext *ctx) {
376 const char* linphone_upnp_context_get_external_ipaddress(UpnpContext *ctx) {
377 return upnp_igd_get_external_ipaddress(ctx->upnp_igd_ctxt);
380 int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port) {
381 upnp_igd_port_mapping mapping;
382 char description[128];
385 // Compute port binding state
386 if(port->state != LinphoneUpnpStateAdding) {
387 port->to_remove = FALSE;
388 switch(port->state) {
389 case LinphoneUpnpStateKo:
390 case LinphoneUpnpStateIdle: {
392 port->state = LinphoneUpnpStateAdding;
395 case LinphoneUpnpStateRemoving: {
405 if(port->retry >= UPNP_ADD_MAX_RETRY) {
408 mapping.cookie = linphone_upnp_port_binding_retain(port);
409 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
411 mapping.local_port = port->local_port;
412 mapping.local_host = port->local_addr;
413 if(port->external_port == -1)
414 mapping.remote_port = rand()%(0xffff - 1024) + 1024;
416 mapping.remote_port = port->external_port;
417 mapping.remote_host = "";
418 snprintf(description, 128, "%s %s at %s:%d",
420 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP": "UDP",
421 port->local_addr, port->local_port);
422 mapping.description = description;
423 mapping.protocol = port->protocol;
426 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to add port binding", port);
427 ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
430 port->state = LinphoneUpnpStateKo;
435 int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port) {
436 upnp_igd_port_mapping mapping;
439 // Compute port binding state
440 if(port->state != LinphoneUpnpStateRemoving) {
441 port->to_add = FALSE;
442 switch(port->state) {
443 case LinphoneUpnpStateOk: {
445 port->state = LinphoneUpnpStateRemoving;
448 case LinphoneUpnpStateAdding: {
449 port->to_remove = TRUE;
458 if(port->retry >= UPNP_REMOVE_MAX_RETRY) {
461 mapping.cookie = linphone_upnp_port_binding_retain(port);
462 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
464 mapping.remote_port = port->external_port;
465 mapping.remote_host = "";
466 mapping.protocol = port->protocol;
468 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to remove port binding", port);
469 ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
472 port->state = LinphoneUpnpStateKo;
478 * uPnP Core interfaces
481 int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool_t video) {
482 LinphoneCore *lc = call->core;
483 UpnpContext *lupnp = lc->upnp;
485 const char *local_addr, *external_addr;
491 ms_mutex_lock(&lupnp->mutex);
492 // Don't handle when the call
493 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
495 local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
496 external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
501 strncpy(call->upnp_session->audio->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
502 strncpy(call->upnp_session->audio->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
503 call->upnp_session->audio->rtp->local_port = call->audio_port;
504 if(call->upnp_session->audio->rtp->external_port == -1) {
505 call->upnp_session->audio->rtp->external_port = call->audio_port;
507 strncpy(call->upnp_session->audio->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
508 strncpy(call->upnp_session->audio->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
509 call->upnp_session->audio->rtcp->local_port = call->audio_port+1;
510 if(call->upnp_session->audio->rtcp->external_port == -1) {
511 call->upnp_session->audio->rtcp->external_port = call->audio_port+1;
514 // Add audio port binding
515 linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->audio->rtp);
516 linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->audio->rtcp);
518 // Remove audio port binding
519 linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->audio->rtp);
520 linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->audio->rtcp);
526 strncpy(call->upnp_session->video->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
527 strncpy(call->upnp_session->video->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
528 call->upnp_session->video->rtp->local_port = call->video_port;
529 if(call->upnp_session->video->rtp->external_port == -1) {
530 call->upnp_session->video->rtp->external_port = call->video_port;
532 strncpy(call->upnp_session->video->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
533 strncpy(call->upnp_session->video->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
534 call->upnp_session->video->rtcp->local_port = call->video_port+1;
535 if(call->upnp_session->video->rtcp->external_port == -1) {
536 call->upnp_session->video->rtcp->external_port = call->video_port+1;
539 // Add video port binding
540 linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->video->rtp);
541 linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->video->rtcp);
543 // Remove video port binding
544 linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->video->rtp);
545 linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->video->rtcp);
549 ms_mutex_unlock(&lupnp->mutex);
552 * Update uPnP call state
554 linphone_upnp_call_process(call);
560 int linphone_core_update_upnp_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) {
561 bool_t audio = FALSE;
562 bool_t video = FALSE;
564 const SalStreamDescription *stream;
566 for (i = 0; i < md->nstreams; i++) {
567 stream = &md->streams[i];
568 if(stream->type == SalAudio) {
570 } else if(stream->type == SalVideo) {
575 return linphone_core_update_upnp_audio_video(call, audio, video);
578 int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
579 return linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
582 void linphone_core_update_upnp_state_in_call_stats(LinphoneCall *call) {
583 call->stats[LINPHONE_CALL_STATS_AUDIO].upnp_state = call->upnp_session->audio->state;
584 call->stats[LINPHONE_CALL_STATS_VIDEO].upnp_state = call->upnp_session->video->state;
587 int linphone_upnp_call_process(LinphoneCall *call) {
588 LinphoneCore *lc = call->core;
589 UpnpContext *lupnp = lc->upnp;
591 LinphoneUpnpState oldState = 0, newState = 0;
597 ms_mutex_lock(&lupnp->mutex);
599 // Don't handle when the call
600 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
606 if((call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle) &&
607 (call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle)) {
608 call->upnp_session->audio->state = LinphoneUpnpStateOk;
609 } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateAdding ||
610 call->upnp_session->audio->rtp->state == LinphoneUpnpStateRemoving ||
611 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateAdding ||
612 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateRemoving) {
613 call->upnp_session->audio->state = LinphoneUpnpStatePending;
614 } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateKo ||
615 call->upnp_session->audio->rtp->state == LinphoneUpnpStateKo) {
616 call->upnp_session->audio->state = LinphoneUpnpStateKo;
618 call->upnp_session->audio->state = LinphoneUpnpStateIdle;
624 if((call->upnp_session->video->rtp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle) &&
625 (call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle)) {
626 call->upnp_session->video->state = LinphoneUpnpStateOk;
627 } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateAdding ||
628 call->upnp_session->video->rtp->state == LinphoneUpnpStateRemoving ||
629 call->upnp_session->video->rtcp->state == LinphoneUpnpStateAdding ||
630 call->upnp_session->video->rtcp->state == LinphoneUpnpStateRemoving) {
631 call->upnp_session->video->state = LinphoneUpnpStatePending;
632 } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateKo ||
633 call->upnp_session->video->rtp->state == LinphoneUpnpStateKo) {
634 call->upnp_session->video->state = LinphoneUpnpStateKo;
636 call->upnp_session->video->state = LinphoneUpnpStateIdle;
640 * Update session state
642 oldState = call->upnp_session->state;
643 if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
644 call->upnp_session->video->state == LinphoneUpnpStateOk) {
645 call->upnp_session->state = LinphoneUpnpStateOk;
646 } else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
647 call->upnp_session->video->state == LinphoneUpnpStatePending) {
648 call->upnp_session->state = LinphoneUpnpStatePending;
649 } else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
650 call->upnp_session->video->state == LinphoneUpnpStateKo) {
651 call->upnp_session->state = LinphoneUpnpStateKo;
653 call->upnp_session->state = LinphoneUpnpStateIdle;
655 newState = call->upnp_session->state;
657 /* When change is done proceed update */
658 if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
659 (call->upnp_session->state == LinphoneUpnpStateOk || call->upnp_session->state == LinphoneUpnpStateKo)) {
660 if(call->upnp_session->state == LinphoneUpnpStateOk)
661 ms_message("uPnP IGD: uPnP for Call %p is ok", call);
663 ms_message("uPnP IGD: uPnP for Call %p is ko", call);
665 switch (call->state) {
666 case LinphoneCallUpdating:
667 linphone_core_start_update_call(lc, call);
669 case LinphoneCallUpdatedByRemote:
670 linphone_core_start_accept_call_update(lc, call);
672 case LinphoneCallOutgoingInit:
673 linphone_core_proceed_with_invite_if_ready(lc, call, NULL);
675 case LinphoneCallIdle:
676 linphone_core_notify_incoming_call(lc, call);
684 ms_mutex_unlock(&lupnp->mutex);
687 * Update uPnP call stats
689 if(oldState != newState) {
690 linphone_core_update_upnp_state_in_call_stats(call);
696 void linphone_core_upnp_refresh(UpnpContext *lupnp) {
697 MSList *global_list = NULL;
701 UpnpPortBinding *port_mapping, *port_mapping2;
703 ms_message("uPnP IGD: Refresh mappings");
705 /* Remove context port bindings */
706 if(lupnp->sip_udp != NULL) {
707 global_list = ms_list_append(global_list, lupnp->sip_udp);
709 if(lupnp->sip_tcp != NULL) {
710 global_list = ms_list_append(global_list, lupnp->sip_tcp);
712 if(lupnp->sip_tls != NULL) {
713 global_list = ms_list_append(global_list, lupnp->sip_tls);
716 /* Remove call port bindings */
717 list = lupnp->lc->calls;
718 while(list != NULL) {
719 call = (LinphoneCall *)list->data;
720 if(call->upnp_session != NULL) {
721 global_list = ms_list_append(global_list, call->upnp_session->audio->rtp);
722 global_list = ms_list_append(global_list, call->upnp_session->audio->rtcp);
723 global_list = ms_list_append(global_list, call->upnp_session->video->rtp);
724 global_list = ms_list_append(global_list, call->upnp_session->video->rtcp);
729 // Remove port binding configurations
730 list = linphone_upnp_config_list_port_bindings(lupnp->lc->config);
731 for(item = list;item != NULL; item = item->next) {
732 port_mapping = (UpnpPortBinding *)item->data;
733 port_mapping2 = linphone_upnp_port_binding_equivalent_in_list(global_list, port_mapping);
734 if(port_mapping2 == NULL) {
735 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping);
736 } else if(port_mapping2->state == LinphoneUpnpStateIdle){
737 /* Force to remove */
738 port_mapping2->state = LinphoneUpnpStateOk;
741 ms_list_for_each(list, (void (*)(void*))linphone_upnp_port_binding_release);
742 list = ms_list_free(list);
745 // (Re)Add removed port bindings
747 while(list != NULL) {
748 port_mapping = (UpnpPortBinding *)list->data;
749 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping);
750 linphone_upnp_context_send_add_port_binding(lupnp, port_mapping);
753 global_list = ms_list_free(global_list);
756 bool_t linphone_core_upnp_hook(void *data) {
759 UpnpPortBinding *port_mapping;
760 UpnpContext *lupnp = (UpnpContext *)data;
761 ms_mutex_lock(&lupnp->mutex);
764 for(item = lupnp->adding_configs;item!=NULL;item=item->next) {
765 port_mapping = (UpnpPortBinding *)item->data;
766 snprintf(key, sizeof(key), "%s-%d-%d",
767 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
768 port_mapping->external_port,
769 port_mapping->local_port);
770 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, "uPnP");
771 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping);
773 ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
774 lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
777 for(item = lupnp->removing_configs;item!=NULL;item=item->next) {
778 port_mapping = (UpnpPortBinding *)item->data;
779 snprintf(key, sizeof(key), "%s-%d-%d",
780 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
781 port_mapping->external_port,
782 port_mapping->local_port);
783 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, NULL);
784 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping);
786 ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
787 lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
789 ms_mutex_unlock(&lupnp->mutex);
793 int linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
795 SalStreamDescription *stream;
796 UpnpStream *upnpStream;
798 for (i = 0; i < desc->nstreams; i++) {
799 stream = &desc->streams[i];
801 if(stream->type == SalAudio) {
802 upnpStream = session->audio;
803 } else if(stream->type == SalVideo) {
804 upnpStream = session->video;
806 if(upnpStream != NULL) {
807 if(upnpStream->rtp->state == LinphoneUpnpStateOk) {
808 strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
809 stream->rtp_port = upnpStream->rtp->external_port;
811 if(upnpStream->rtcp->state == LinphoneUpnpStateOk) {
812 strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
813 stream->rtcp_port = upnpStream->rtcp->external_port;
825 UpnpPortBinding *linphone_upnp_port_binding_new() {
826 UpnpPortBinding *port = NULL;
827 port = ms_new0(UpnpPortBinding,1);
828 ms_mutex_init(&port->mutex, NULL);
829 port->state = LinphoneUpnpStateIdle;
830 port->local_addr[0] = '\0';
831 port->local_port = -1;
832 port->external_addr[0] = '\0';
833 port->external_port = -1;
834 port->to_remove = FALSE;
835 port->to_add = FALSE;
840 UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port) {
841 UpnpPortBinding *new_port = NULL;
842 new_port = ms_new0(UpnpPortBinding,1);
843 memcpy(new_port, port, sizeof(UpnpPortBinding));
844 ms_mutex_init(&new_port->mutex, NULL);
849 void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port) {
850 if(strlen(port->local_addr)) {
851 ortp_log(level, "uPnP IGD: %s %s|%d->%s:%d", msg,
852 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
857 ortp_log(level, "uPnP IGD: %s %s|%d->%d", msg,
858 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
864 bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
865 return port1->protocol == port2->protocol &&
866 port1->local_port == port2->local_port &&
867 port1->external_port == port2->external_port;
870 UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port) {
871 UpnpPortBinding *port_mapping;
872 while(list != NULL) {
873 port_mapping = (UpnpPortBinding *)list->data;
874 if(linphone_upnp_port_binding_equal(port, port_mapping)) {
883 UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port) {
884 ms_mutex_lock(&port->mutex);
886 ms_mutex_unlock(&port->mutex);
890 void linphone_upnp_port_binding_release(UpnpPortBinding *port) {
891 ms_mutex_lock(&port->mutex);
892 if(--port->ref == 0) {
893 ms_mutex_unlock(&port->mutex);
894 ms_mutex_destroy(&port->mutex);
898 ms_mutex_unlock(&port->mutex);
906 UpnpStream* linphone_upnp_stream_new() {
907 UpnpStream *stream = ms_new0(UpnpStream,1);
908 stream->state = LinphoneUpnpStateIdle;
909 stream->rtp = linphone_upnp_port_binding_new();
910 stream->rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
911 stream->rtcp = linphone_upnp_port_binding_new();
912 stream->rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
916 void linphone_upnp_stream_destroy(UpnpStream* stream) {
917 linphone_upnp_port_binding_release(stream->rtp);
919 linphone_upnp_port_binding_release(stream->rtcp);
929 UpnpSession* linphone_upnp_session_new(LinphoneCall* call) {
930 UpnpSession *session = ms_new0(UpnpSession,1);
931 session->call = call;
932 session->state = LinphoneUpnpStateIdle;
933 session->audio = linphone_upnp_stream_new();
934 session->video = linphone_upnp_stream_new();
938 void linphone_upnp_session_destroy(UpnpSession *session) {
939 LinphoneCore *lc = session->call->core;
941 if(lc->upnp != NULL) {
942 /* Remove bindings */
943 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtp);
944 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtcp);
945 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtp);
946 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtcp);
949 linphone_upnp_stream_destroy(session->audio);
950 linphone_upnp_stream_destroy(session->video);
954 LinphoneUpnpState linphone_upnp_session_get_state(UpnpSession *session) {
955 return session->state;
962 struct linphone_upnp_config_list_port_bindings_struct {
963 struct _LpConfig *lpc;
967 static void linphone_upnp_config_list_port_bindings_cb(const char *entry, struct linphone_upnp_config_list_port_bindings_struct *cookie) {
968 char protocol_str[4]; // TCP or UDP
969 upnp_igd_ip_protocol protocol;
973 UpnpPortBinding *port;
974 if(sscanf(entry, "%3s-%i-%i", protocol_str, &external_port, &local_port) == 3) {
975 if(strcasecmp(protocol_str, "TCP") == 0) {
976 protocol = UPNP_IGD_IP_PROTOCOL_TCP;
977 } else if(strcasecmp(protocol_str, "UDP") == 0) {
978 protocol = UPNP_IGD_IP_PROTOCOL_UDP;
983 port = linphone_upnp_port_binding_new();
984 port->state = LinphoneUpnpStateOk;
985 port->protocol = protocol;
986 port->external_port = external_port;
987 port->local_port = local_port;
988 cookie->retList = ms_list_append(cookie->retList, port);
994 ms_warning("uPnP configuration invalid line: %s", entry);
998 MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc) {
999 struct linphone_upnp_config_list_port_bindings_struct cookie = {lpc, NULL};
1000 lp_config_for_each_entry(lpc, UPNP_SECTION_NAME, (void(*)(const char *, void*))linphone_upnp_config_list_port_bindings_cb, &cookie);
1001 return cookie.retList;
1004 void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1006 UpnpPortBinding *list_port;
1008 list = lupnp->removing_configs;
1009 while(list != NULL) {
1010 list_port = (UpnpPortBinding *)list->data;
1011 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1012 lupnp->removing_configs = ms_list_remove(lupnp->removing_configs, list_port);
1013 linphone_upnp_port_binding_release(list_port);
1016 list = ms_list_next(list);
1019 list = lupnp->adding_configs;
1020 while(list != NULL) {
1021 list_port = (UpnpPortBinding *)list->data;
1022 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1025 list = ms_list_next(list);
1028 list_port = linphone_upnp_port_binding_copy(port);
1029 lupnp->adding_configs = ms_list_append(lupnp->adding_configs, list_port);
1032 void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1034 UpnpPortBinding *list_port;
1036 list = lupnp->adding_configs;
1037 while(list != NULL) {
1038 list_port = (UpnpPortBinding *)list->data;
1039 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1040 lupnp->adding_configs = ms_list_remove(lupnp->adding_configs, list_port);
1041 linphone_upnp_port_binding_release(list_port);
1044 list = ms_list_next(list);
1047 list = lupnp->removing_configs;
1048 while(list != NULL) {
1049 list_port = (UpnpPortBinding *)list->data;
1050 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1053 list = ms_list_next(list);
1056 list_port = linphone_upnp_port_binding_copy(port);
1057 lupnp->removing_configs = ms_list_append(lupnp->removing_configs, list_port);