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_DEVICE_ADDED:
137 case UPNP_IGD_DEVICE_REMOVED:
138 case UPNP_IGD_EXTERNAL_IPADDRESS_CHANGED:
139 case UPNP_IGD_NAT_ENABLED_CHANGED:
140 case UPNP_IGD_CONNECTION_STATUS_CHANGED:
141 ip_address = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
142 connection_status = upnp_igd_get_connection_status(lupnp->upnp_igd_ctxt);
143 nat_enabled = upnp_igd_get_nat_enabled(lupnp->upnp_igd_ctxt);
145 if(ip_address == NULL || connection_status == NULL) {
146 ms_message("uPnP IGD: Pending");
147 lupnp->state = LinphoneUpnpStatePending;
148 } else if(strcasecmp(connection_status, "Connected") || !nat_enabled) {
149 ms_message("uPnP IGD: Not Available");
150 lupnp->state = LinphoneUpnpStateNotAvailable;
152 ms_message("uPnP IGD: Connected");
153 lupnp->state = LinphoneUpnpStateOk;
154 if(old_state != LinphoneUpnpStateOk) {
155 linphone_core_upnp_refresh(lupnp);
161 case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS:
162 mapping = (upnp_igd_port_mapping *) arg;
163 port_mapping = (UpnpPortBinding*) mapping->cookie;
164 port_mapping->external_port = mapping->remote_port;
165 port_mapping->state = LinphoneUpnpStateOk;
166 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Added port binding", port_mapping);
167 linphone_upnp_config_add_port_binding(lupnp, port_mapping);
171 case UPNP_IGD_PORT_MAPPING_ADD_FAILURE:
172 mapping = (upnp_igd_port_mapping *) arg;
173 port_mapping = (UpnpPortBinding*) mapping->cookie;
174 port_mapping->external_port = -1; //Force random external port
175 if(linphone_upnp_context_send_add_port_binding(lupnp, port_mapping) != 0) {
176 linphone_upnp_port_binding_log(ORTP_ERROR, "Can't add port binding", port_mapping);
181 case UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS:
182 mapping = (upnp_igd_port_mapping *) arg;
183 port_mapping = (UpnpPortBinding*) mapping->cookie;
184 port_mapping->state = LinphoneUpnpStateIdle;
185 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Removed port binding", port_mapping);
186 linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
190 case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE:
191 mapping = (upnp_igd_port_mapping *) arg;
192 port_mapping = (UpnpPortBinding*) mapping->cookie;
193 if(linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping) != 0) {
194 linphone_upnp_port_binding_log(ORTP_ERROR, "Can't remove port binding", port_mapping);
195 linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
204 if(port_mapping != NULL) {
206 * Execute delayed actions
208 if(port_mapping->to_remove) {
209 if(port_mapping->state == LinphoneUpnpStateOk) {
210 port_mapping->to_remove = FALSE;
211 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping);
212 } else if(port_mapping->state == LinphoneUpnpStateKo) {
213 port_mapping->to_remove = FALSE;
216 if(port_mapping->to_add) {
217 if(port_mapping->state == LinphoneUpnpStateIdle || port_mapping->state == LinphoneUpnpStateKo) {
218 port_mapping->to_add = FALSE;
219 linphone_upnp_context_send_add_port_binding(lupnp, port_mapping);
223 lupnp->pending_bindings = ms_list_remove(lupnp->pending_bindings, port_mapping);
224 linphone_upnp_port_binding_release(port_mapping);
228 * If there is no pending binding emit a signal
230 if(lupnp->pending_bindings == NULL) {
231 pthread_cond_signal(&lupnp->empty_cond);
233 ms_mutex_unlock(&lupnp->mutex);
241 UpnpContext* linphone_upnp_context_new(LinphoneCore *lc) {
242 LCSipTransports transport;
243 UpnpContext *lupnp = (UpnpContext *)ms_new0(UpnpContext,1);
244 const char *ip_address;
246 ms_mutex_init(&lupnp->mutex, NULL);
247 ms_cond_init(&lupnp->empty_cond, NULL);
250 lupnp->pending_bindings = NULL;
251 lupnp->adding_configs = NULL;
252 lupnp->removing_configs = NULL;
253 lupnp->state = LinphoneUpnpStateIdle;
254 ms_message("uPnP IGD: New %p for core %p", lupnp, lc);
256 linphone_core_get_sip_transports(lc, &transport);
257 if(transport.udp_port != 0) {
258 lupnp->sip_udp = linphone_upnp_port_binding_new();
259 lupnp->sip_udp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
260 lupnp->sip_udp->local_port = transport.udp_port;
261 lupnp->sip_udp->external_port = transport.udp_port;
263 lupnp->sip_udp = NULL;
265 if(transport.tcp_port != 0) {
266 lupnp->sip_tcp = linphone_upnp_port_binding_new();
267 lupnp->sip_tcp->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
268 lupnp->sip_tcp->local_port = transport.tcp_port;
269 lupnp->sip_tcp->external_port = transport.tcp_port;
271 lupnp->sip_tcp = NULL;
273 if(transport.tls_port != 0) {
274 lupnp->sip_tls = linphone_upnp_port_binding_new();
275 lupnp->sip_tls->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
276 lupnp->sip_tls->local_port = transport.tls_port;
277 lupnp->sip_tls->external_port = transport.tls_port;
279 lupnp->sip_tls = NULL;
282 linphone_core_add_iterate_hook(lc, linphone_core_upnp_hook, lupnp);
284 lupnp->upnp_igd_ctxt = NULL;
285 lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, lupnp);
286 if(lupnp->upnp_igd_ctxt == NULL) {
287 lupnp->state = LinphoneUpnpStateKo;
288 ms_error("Can't create uPnP IGD context");
292 ip_address = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
293 if(lupnp->sip_udp != NULL) {
294 strncpy(lupnp->sip_udp->local_addr, ip_address, sizeof(lupnp->sip_udp->local_addr));
296 if(lupnp->sip_tcp != NULL) {
297 strncpy(lupnp->sip_tcp->local_addr, ip_address, sizeof(lupnp->sip_tcp->local_addr));
299 if(lupnp->sip_tls != NULL) {
300 strncpy(lupnp->sip_tls->local_addr, ip_address, sizeof(lupnp->sip_tls->local_addr));
303 lupnp->state = LinphoneUpnpStatePending;
304 upnp_igd_start(lupnp->upnp_igd_ctxt);
309 void linphone_upnp_context_destroy(UpnpContext *lupnp) {
311 * Not need, all hooks are removed before
312 * linphone_core_remove_iterate_hook(lc, linphone_core_upnp_hook, lc);
315 ms_mutex_lock(&lupnp->mutex);
317 /* Send port binding removes */
318 if(lupnp->sip_udp != NULL) {
319 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_udp);
321 if(lupnp->sip_tcp != NULL) {
322 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tcp);
324 if(lupnp->sip_tls != NULL) {
325 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tls);
328 /* Wait all pending bindings are done */
329 if(lupnp->pending_bindings != NULL) {
330 ms_message("uPnP IGD: Wait all pending port bindings ...");
331 ms_cond_wait(&lupnp->empty_cond, &lupnp->mutex);
333 ms_mutex_unlock(&lupnp->mutex);
335 if(lupnp->upnp_igd_ctxt != NULL) {
336 upnp_igd_destroy(lupnp->upnp_igd_ctxt);
339 /* Run one time the hook for configuration update */
340 linphone_core_upnp_hook(lupnp);
342 /* Release port bindings */
343 if(lupnp->sip_udp != NULL) {
344 linphone_upnp_port_binding_release(lupnp->sip_udp);
345 lupnp->sip_udp = NULL;
347 if(lupnp->sip_tcp != NULL) {
348 linphone_upnp_port_binding_release(lupnp->sip_tcp);
349 lupnp->sip_tcp = NULL;
351 if(lupnp->sip_tls != NULL) {
352 linphone_upnp_port_binding_release(lupnp->sip_tls);
353 lupnp->sip_tcp = NULL;
357 ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
358 lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
359 ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
360 lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
361 ms_list_for_each(lupnp->pending_bindings,(void (*)(void*))linphone_upnp_port_binding_release);
362 lupnp->pending_bindings = ms_list_free(lupnp->pending_bindings);
364 ms_mutex_destroy(&lupnp->mutex);
365 ms_cond_destroy(&lupnp->empty_cond);
367 ms_message("uPnP IGD: destroy %p", lupnp);
371 LinphoneUpnpState linphone_upnp_context_get_state(UpnpContext *ctx) {
375 const char* linphone_upnp_context_get_external_ipaddress(UpnpContext *ctx) {
376 return upnp_igd_get_external_ipaddress(ctx->upnp_igd_ctxt);
379 int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port) {
380 upnp_igd_port_mapping mapping;
381 char description[128];
384 if(lupnp->state != LinphoneUpnpStateOk) {
388 // Compute port binding state
389 if(port->state != LinphoneUpnpStateAdding) {
390 port->to_remove = FALSE;
391 switch(port->state) {
392 case LinphoneUpnpStateKo:
393 case LinphoneUpnpStateIdle: {
395 port->state = LinphoneUpnpStateAdding;
398 case LinphoneUpnpStateRemoving: {
408 if(port->retry >= UPNP_ADD_MAX_RETRY) {
411 mapping.cookie = linphone_upnp_port_binding_retain(port);
412 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
414 mapping.local_port = port->local_port;
415 mapping.local_host = port->local_addr;
416 if(port->external_port == -1)
417 mapping.remote_port = rand()%(0xffff - 1024) + 1024;
419 mapping.remote_port = port->external_port;
420 mapping.remote_host = "";
421 snprintf(description, 128, "%s %s at %s:%d",
423 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP": "UDP",
424 port->local_addr, port->local_port);
425 mapping.description = description;
426 mapping.protocol = port->protocol;
429 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to add port binding", port);
430 ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
433 port->state = LinphoneUpnpStateKo;
438 int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port) {
439 upnp_igd_port_mapping mapping;
442 if(lupnp->state != LinphoneUpnpStateOk) {
446 // Compute port binding state
447 if(port->state != LinphoneUpnpStateRemoving) {
448 port->to_add = FALSE;
449 switch(port->state) {
450 case LinphoneUpnpStateOk: {
452 port->state = LinphoneUpnpStateRemoving;
455 case LinphoneUpnpStateAdding: {
456 port->to_remove = TRUE;
465 if(port->retry >= UPNP_REMOVE_MAX_RETRY) {
468 mapping.cookie = linphone_upnp_port_binding_retain(port);
469 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
471 mapping.remote_port = port->external_port;
472 mapping.remote_host = "";
473 mapping.protocol = port->protocol;
475 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to remove port binding", port);
476 ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
479 port->state = LinphoneUpnpStateKo;
485 * uPnP Core interfaces
488 int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool_t video) {
489 LinphoneCore *lc = call->core;
490 UpnpContext *lupnp = lc->upnp;
492 const char *local_addr, *external_addr;
498 ms_mutex_lock(&lupnp->mutex);
499 // Don't handle when the call
500 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
502 local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
503 external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
508 strncpy(call->upnp_session->audio->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
509 strncpy(call->upnp_session->audio->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
510 call->upnp_session->audio->rtp->local_port = call->audio_port;
511 if(call->upnp_session->audio->rtp->external_port == -1) {
512 call->upnp_session->audio->rtp->external_port = call->audio_port;
514 strncpy(call->upnp_session->audio->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
515 strncpy(call->upnp_session->audio->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
516 call->upnp_session->audio->rtcp->local_port = call->audio_port+1;
517 if(call->upnp_session->audio->rtcp->external_port == -1) {
518 call->upnp_session->audio->rtcp->external_port = call->audio_port+1;
521 // Add audio port binding
522 linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->audio->rtp);
523 linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->audio->rtcp);
525 // Remove audio port binding
526 linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->audio->rtp);
527 linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->audio->rtcp);
533 strncpy(call->upnp_session->video->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
534 strncpy(call->upnp_session->video->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
535 call->upnp_session->video->rtp->local_port = call->video_port;
536 if(call->upnp_session->video->rtp->external_port == -1) {
537 call->upnp_session->video->rtp->external_port = call->video_port;
539 strncpy(call->upnp_session->video->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
540 strncpy(call->upnp_session->video->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
541 call->upnp_session->video->rtcp->local_port = call->video_port+1;
542 if(call->upnp_session->video->rtcp->external_port == -1) {
543 call->upnp_session->video->rtcp->external_port = call->video_port+1;
546 // Add video port binding
547 linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->video->rtp);
548 linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->video->rtcp);
550 // Remove video port binding
551 linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->video->rtp);
552 linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->video->rtcp);
556 ms_mutex_unlock(&lupnp->mutex);
559 * Update uPnP call state
561 linphone_upnp_call_process(call);
567 int linphone_core_update_upnp_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) {
568 bool_t audio = FALSE;
569 bool_t video = FALSE;
571 const SalStreamDescription *stream;
573 for (i = 0; i < md->n_total_streams; i++) {
574 stream = &md->streams[i];
575 if(stream->type == SalAudio) {
577 } else if(stream->type == SalVideo) {
582 return linphone_core_update_upnp_audio_video(call, audio, video);
585 int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
586 return linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
589 void linphone_core_update_upnp_state_in_call_stats(LinphoneCall *call) {
590 call->stats[LINPHONE_CALL_STATS_AUDIO].upnp_state = call->upnp_session->audio->state;
591 call->stats[LINPHONE_CALL_STATS_VIDEO].upnp_state = call->upnp_session->video->state;
594 int linphone_upnp_call_process(LinphoneCall *call) {
595 LinphoneCore *lc = call->core;
596 UpnpContext *lupnp = lc->upnp;
598 LinphoneUpnpState oldState = 0, newState = 0;
604 ms_mutex_lock(&lupnp->mutex);
606 // Don't handle when the call
607 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
613 if((call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle) &&
614 (call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle)) {
615 call->upnp_session->audio->state = LinphoneUpnpStateOk;
616 } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateAdding ||
617 call->upnp_session->audio->rtp->state == LinphoneUpnpStateRemoving ||
618 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateAdding ||
619 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateRemoving) {
620 call->upnp_session->audio->state = LinphoneUpnpStatePending;
621 } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateKo ||
622 call->upnp_session->audio->rtp->state == LinphoneUpnpStateKo) {
623 call->upnp_session->audio->state = LinphoneUpnpStateKo;
625 call->upnp_session->audio->state = LinphoneUpnpStateIdle;
631 if((call->upnp_session->video->rtp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle) &&
632 (call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle)) {
633 call->upnp_session->video->state = LinphoneUpnpStateOk;
634 } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateAdding ||
635 call->upnp_session->video->rtp->state == LinphoneUpnpStateRemoving ||
636 call->upnp_session->video->rtcp->state == LinphoneUpnpStateAdding ||
637 call->upnp_session->video->rtcp->state == LinphoneUpnpStateRemoving) {
638 call->upnp_session->video->state = LinphoneUpnpStatePending;
639 } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateKo ||
640 call->upnp_session->video->rtp->state == LinphoneUpnpStateKo) {
641 call->upnp_session->video->state = LinphoneUpnpStateKo;
643 call->upnp_session->video->state = LinphoneUpnpStateIdle;
647 * Update session state
649 oldState = call->upnp_session->state;
650 if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
651 call->upnp_session->video->state == LinphoneUpnpStateOk) {
652 call->upnp_session->state = LinphoneUpnpStateOk;
653 } else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
654 call->upnp_session->video->state == LinphoneUpnpStatePending) {
655 call->upnp_session->state = LinphoneUpnpStatePending;
656 } else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
657 call->upnp_session->video->state == LinphoneUpnpStateKo) {
658 call->upnp_session->state = LinphoneUpnpStateKo;
660 call->upnp_session->state = LinphoneUpnpStateIdle;
662 newState = call->upnp_session->state;
664 /* When change is done proceed update */
665 if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
666 (call->upnp_session->state == LinphoneUpnpStateOk || call->upnp_session->state == LinphoneUpnpStateKo)) {
667 if(call->upnp_session->state == LinphoneUpnpStateOk)
668 ms_message("uPnP IGD: uPnP for Call %p is ok", call);
670 ms_message("uPnP IGD: uPnP for Call %p is ko", call);
672 switch (call->state) {
673 case LinphoneCallUpdating:
674 linphone_core_start_update_call(lc, call);
676 case LinphoneCallUpdatedByRemote:
677 linphone_core_start_accept_call_update(lc, call);
679 case LinphoneCallOutgoingInit:
680 linphone_core_proceed_with_invite_if_ready(lc, call, NULL);
682 case LinphoneCallIdle:
683 linphone_core_notify_incoming_call(lc, call);
691 ms_mutex_unlock(&lupnp->mutex);
694 * Update uPnP call stats
696 if(oldState != newState) {
697 linphone_core_update_upnp_state_in_call_stats(call);
703 void linphone_core_upnp_refresh(UpnpContext *lupnp) {
704 MSList *global_list = NULL;
708 UpnpPortBinding *port_mapping, *port_mapping2;
710 ms_message("uPnP IGD: Refresh mappings");
712 /* Remove context port bindings */
713 if(lupnp->sip_udp != NULL) {
714 global_list = ms_list_append(global_list, lupnp->sip_udp);
716 if(lupnp->sip_tcp != NULL) {
717 global_list = ms_list_append(global_list, lupnp->sip_tcp);
719 if(lupnp->sip_tls != NULL) {
720 global_list = ms_list_append(global_list, lupnp->sip_tls);
723 /* Remove call port bindings */
724 list = lupnp->lc->calls;
725 while(list != NULL) {
726 call = (LinphoneCall *)list->data;
727 if(call->upnp_session != NULL) {
728 global_list = ms_list_append(global_list, call->upnp_session->audio->rtp);
729 global_list = ms_list_append(global_list, call->upnp_session->audio->rtcp);
730 global_list = ms_list_append(global_list, call->upnp_session->video->rtp);
731 global_list = ms_list_append(global_list, call->upnp_session->video->rtcp);
736 // Remove port binding configurations
737 list = linphone_upnp_config_list_port_bindings(lupnp->lc->config);
738 for(item = list;item != NULL; item = item->next) {
739 port_mapping = (UpnpPortBinding *)item->data;
740 port_mapping2 = linphone_upnp_port_binding_equivalent_in_list(global_list, port_mapping);
741 if(port_mapping2 == NULL) {
742 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping);
743 } else if(port_mapping2->state == LinphoneUpnpStateIdle){
744 /* Force to remove */
745 port_mapping2->state = LinphoneUpnpStateOk;
748 ms_list_for_each(list, (void (*)(void*))linphone_upnp_port_binding_release);
749 list = ms_list_free(list);
752 // (Re)Add removed port bindings
754 while(list != NULL) {
755 port_mapping = (UpnpPortBinding *)list->data;
756 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping);
757 linphone_upnp_context_send_add_port_binding(lupnp, port_mapping);
760 global_list = ms_list_free(global_list);
763 bool_t linphone_core_upnp_hook(void *data) {
766 UpnpPortBinding *port_mapping;
767 UpnpContext *lupnp = (UpnpContext *)data;
768 ms_mutex_lock(&lupnp->mutex);
771 for(item = lupnp->adding_configs;item!=NULL;item=item->next) {
772 port_mapping = (UpnpPortBinding *)item->data;
773 snprintf(key, sizeof(key), "%s-%d-%d",
774 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
775 port_mapping->external_port,
776 port_mapping->local_port);
777 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, "uPnP");
778 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping);
780 ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
781 lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
784 for(item = lupnp->removing_configs;item!=NULL;item=item->next) {
785 port_mapping = (UpnpPortBinding *)item->data;
786 snprintf(key, sizeof(key), "%s-%d-%d",
787 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
788 port_mapping->external_port,
789 port_mapping->local_port);
790 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, NULL);
791 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping);
793 ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
794 lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
796 ms_mutex_unlock(&lupnp->mutex);
800 int linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
802 SalStreamDescription *stream;
803 UpnpStream *upnpStream;
805 for (i = 0; i < desc->n_active_streams; i++) {
806 stream = &desc->streams[i];
808 if(stream->type == SalAudio) {
809 upnpStream = session->audio;
810 } else if(stream->type == SalVideo) {
811 upnpStream = session->video;
813 if(upnpStream != NULL) {
814 if(upnpStream->rtp->state == LinphoneUpnpStateOk) {
815 strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
816 stream->rtp_port = upnpStream->rtp->external_port;
818 if(upnpStream->rtcp->state == LinphoneUpnpStateOk) {
819 strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
820 stream->rtcp_port = upnpStream->rtcp->external_port;
832 UpnpPortBinding *linphone_upnp_port_binding_new() {
833 UpnpPortBinding *port = NULL;
834 port = ms_new0(UpnpPortBinding,1);
835 ms_mutex_init(&port->mutex, NULL);
836 port->state = LinphoneUpnpStateIdle;
837 port->local_addr[0] = '\0';
838 port->local_port = -1;
839 port->external_addr[0] = '\0';
840 port->external_port = -1;
841 port->to_remove = FALSE;
842 port->to_add = FALSE;
847 UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port) {
848 UpnpPortBinding *new_port = NULL;
849 new_port = ms_new0(UpnpPortBinding,1);
850 memcpy(new_port, port, sizeof(UpnpPortBinding));
851 ms_mutex_init(&new_port->mutex, NULL);
856 void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port) {
857 if(strlen(port->local_addr)) {
858 ortp_log(level, "uPnP IGD: %s %s|%d->%s:%d (retry %d)", msg,
859 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
865 ortp_log(level, "uPnP IGD: %s %s|%d->%d (retry %d)", msg,
866 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
873 bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
874 return port1->protocol == port2->protocol &&
875 port1->local_port == port2->local_port &&
876 port1->external_port == port2->external_port;
879 UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port) {
880 UpnpPortBinding *port_mapping;
881 while(list != NULL) {
882 port_mapping = (UpnpPortBinding *)list->data;
883 if(linphone_upnp_port_binding_equal(port, port_mapping)) {
892 UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port) {
893 ms_mutex_lock(&port->mutex);
895 ms_mutex_unlock(&port->mutex);
899 void linphone_upnp_port_binding_release(UpnpPortBinding *port) {
900 ms_mutex_lock(&port->mutex);
901 if(--port->ref == 0) {
902 ms_mutex_unlock(&port->mutex);
903 ms_mutex_destroy(&port->mutex);
907 ms_mutex_unlock(&port->mutex);
915 UpnpStream* linphone_upnp_stream_new() {
916 UpnpStream *stream = ms_new0(UpnpStream,1);
917 stream->state = LinphoneUpnpStateIdle;
918 stream->rtp = linphone_upnp_port_binding_new();
919 stream->rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
920 stream->rtcp = linphone_upnp_port_binding_new();
921 stream->rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
925 void linphone_upnp_stream_destroy(UpnpStream* stream) {
926 linphone_upnp_port_binding_release(stream->rtp);
928 linphone_upnp_port_binding_release(stream->rtcp);
938 UpnpSession* linphone_upnp_session_new(LinphoneCall* call) {
939 UpnpSession *session = ms_new0(UpnpSession,1);
940 session->call = call;
941 session->state = LinphoneUpnpStateIdle;
942 session->audio = linphone_upnp_stream_new();
943 session->video = linphone_upnp_stream_new();
947 void linphone_upnp_session_destroy(UpnpSession *session) {
948 LinphoneCore *lc = session->call->core;
950 if(lc->upnp != NULL) {
951 /* Remove bindings */
952 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtp);
953 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtcp);
954 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtp);
955 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtcp);
958 linphone_upnp_stream_destroy(session->audio);
959 linphone_upnp_stream_destroy(session->video);
963 LinphoneUpnpState linphone_upnp_session_get_state(UpnpSession *session) {
964 return session->state;
971 struct linphone_upnp_config_list_port_bindings_struct {
972 struct _LpConfig *lpc;
976 static void linphone_upnp_config_list_port_bindings_cb(const char *entry, struct linphone_upnp_config_list_port_bindings_struct *cookie) {
977 char protocol_str[4]; // TCP or UDP
978 upnp_igd_ip_protocol protocol;
982 UpnpPortBinding *port;
983 if(sscanf(entry, "%3s-%i-%i", protocol_str, &external_port, &local_port) == 3) {
984 if(strcasecmp(protocol_str, "TCP") == 0) {
985 protocol = UPNP_IGD_IP_PROTOCOL_TCP;
986 } else if(strcasecmp(protocol_str, "UDP") == 0) {
987 protocol = UPNP_IGD_IP_PROTOCOL_UDP;
992 port = linphone_upnp_port_binding_new();
993 port->state = LinphoneUpnpStateOk;
994 port->protocol = protocol;
995 port->external_port = external_port;
996 port->local_port = local_port;
997 cookie->retList = ms_list_append(cookie->retList, port);
1003 ms_warning("uPnP configuration invalid line: %s", entry);
1007 MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc) {
1008 struct linphone_upnp_config_list_port_bindings_struct cookie = {lpc, NULL};
1009 lp_config_for_each_entry(lpc, UPNP_SECTION_NAME, (void(*)(const char *, void*))linphone_upnp_config_list_port_bindings_cb, &cookie);
1010 return cookie.retList;
1013 void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1015 UpnpPortBinding *list_port;
1017 list = lupnp->removing_configs;
1018 while(list != NULL) {
1019 list_port = (UpnpPortBinding *)list->data;
1020 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1021 lupnp->removing_configs = ms_list_remove(lupnp->removing_configs, list_port);
1022 linphone_upnp_port_binding_release(list_port);
1025 list = ms_list_next(list);
1028 list = lupnp->adding_configs;
1029 while(list != NULL) {
1030 list_port = (UpnpPortBinding *)list->data;
1031 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1034 list = ms_list_next(list);
1037 list_port = linphone_upnp_port_binding_copy(port);
1038 lupnp->adding_configs = ms_list_append(lupnp->adding_configs, list_port);
1041 void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1043 UpnpPortBinding *list_port;
1045 list = lupnp->adding_configs;
1046 while(list != NULL) {
1047 list_port = (UpnpPortBinding *)list->data;
1048 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1049 lupnp->adding_configs = ms_list_remove(lupnp->adding_configs, list_port);
1050 linphone_upnp_port_binding_release(list_port);
1053 list = ms_list_next(list);
1056 list = lupnp->removing_configs;
1057 while(list != NULL) {
1058 list_port = (UpnpPortBinding *)list->data;
1059 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1062 list = ms_list_next(list);
1065 list_port = linphone_upnp_port_binding_copy(port);
1066 lupnp->removing_configs = ms_list_append(lupnp->removing_configs, list_port);