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.
23 #define UPNP_ADD_MAX_RETRY 4
24 #define UPNP_REMOVE_MAX_RETRY 4
25 #define UPNP_SECTION_NAME "uPnP"
27 /* Define private types */
28 typedef struct _LpItem{
33 typedef struct _LpSection{
38 typedef struct _LpConfig{
46 /* Declare private functions */
47 LpSection *lp_config_find_section(LpConfig *lpconfig, const char *name);
48 void lp_section_remove_item(LpSection *sec, LpItem *item);
49 void lp_config_set_string(LpConfig *lpconfig,const char *section, const char *key, const char *value);
56 typedef struct _UpnpPortBinding {
59 upnp_igd_ip_protocol protocol;
60 char local_addr[LINPHONE_IPADDR_SIZE];
62 char external_addr[LINPHONE_IPADDR_SIZE];
68 typedef struct _UpnpStream {
70 UpnpPortBinding *rtcp;
83 upnp_igd_context *upnp_igd_ctxt;
84 UpnpPortBinding *sip_tcp;
85 UpnpPortBinding *sip_tls;
86 UpnpPortBinding *sip_udp;
88 MSList *removing_configs;
89 MSList *adding_configs;
90 MSList *pending_bindings;
92 bool_t clean; // True if at the next loop clean the port bindings
93 bool_t cleaning; // True if the cleaning processing;
94 bool_t emit; // True if at the next loop emit the port bindings
102 bool_t linphone_core_upnp_hook(void *data);
104 UpnpPortBinding *upnp_port_binding_new();
105 UpnpPortBinding *upnp_port_binding_copy(const UpnpPortBinding *port);
106 bool_t upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2);
107 UpnpPortBinding *upnp_port_binding_retain(UpnpPortBinding *port);
108 void upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port);
109 void upnp_port_binding_release(UpnpPortBinding *port);
111 MSList *upnp_config_list_port_bindings(struct _LpConfig *lpc);
112 void upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port);
113 void upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port);
115 int upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port);
116 int upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port);
123 /* Convert uPnP IGD logs to ortp logs */
124 void linphone_upnp_igd_print(void *cookie, upnp_igd_print_level level, const char *fmt, va_list list) {
125 int ortp_level = ORTP_DEBUG;
127 case UPNP_IGD_MESSAGE:
128 ortp_level = ORTP_MESSAGE;
130 case UPNP_IGD_WARNING:
131 ortp_level = ORTP_DEBUG; // Too verbose otherwise
134 ortp_level = ORTP_DEBUG; // Too verbose otherwise
139 ortp_logv(ortp_level, fmt, list);
142 void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) {
143 UpnpContext *lupnp = (UpnpContext *)cookie;
144 upnp_igd_port_mapping *mapping = NULL;
145 UpnpPortBinding *port_mapping = NULL;
146 const char *ip_address = NULL;
147 const char *connection_status = NULL;
148 bool_t nat_enabled = FALSE;
149 ms_mutex_lock(&lupnp->mutex);
152 case UPNP_IGD_EXTERNAL_IPADDRESS_CHANGED:
153 case UPNP_IGD_NAT_ENABLED_CHANGED:
154 case UPNP_IGD_CONNECTION_STATUS_CHANGED:
155 ip_address = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
156 connection_status = upnp_igd_get_connection_status(lupnp->upnp_igd_ctxt);
157 nat_enabled = upnp_igd_get_nat_enabled(lupnp->upnp_igd_ctxt);
159 if(ip_address == NULL || connection_status == NULL) {
160 ms_message("uPnP IGD: Pending");
161 lupnp->state = LinphoneUpnpStatePending;
162 } else if(strcasecmp(connection_status, "Connected") || !nat_enabled) {
163 ms_message("uPnP IGD: Not Available");
164 lupnp->state = LinphoneUpnpStateNotAvailable;
166 ms_message("uPnP IGD: Connected");
167 if(lupnp->state != LinphoneUpnpStateOk) {
168 lupnp->clean = TRUE; // Remove saved port mapping configurations
170 lupnp->state = LinphoneUpnpStateOk;
175 case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS:
176 mapping = (upnp_igd_port_mapping *) arg;
177 port_mapping = (UpnpPortBinding*) mapping->cookie;
178 port_mapping->external_port = mapping->remote_port;
179 port_mapping->state = LinphoneUpnpStateOk;
180 upnp_port_binding_log(ORTP_MESSAGE, "Added port binding", port_mapping);
181 upnp_config_add_port_binding(lupnp, port_mapping);
183 lupnp->pending_bindings = ms_list_remove(lupnp->pending_bindings, port_mapping);
184 upnp_port_binding_release(port_mapping);
187 case UPNP_IGD_PORT_MAPPING_ADD_FAILURE:
188 mapping = (upnp_igd_port_mapping *) arg;
189 port_mapping = (UpnpPortBinding*) mapping->cookie;
190 port_mapping->external_port = -1; //Force random external port
191 if(upnp_context_send_add_port_binding(lupnp, port_mapping) != 0) {
192 upnp_port_binding_log(ORTP_ERROR, "Can't add port binding", port_mapping);
195 lupnp->pending_bindings = ms_list_remove(lupnp->pending_bindings, port_mapping);
196 upnp_port_binding_release(port_mapping);
199 case UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS:
200 mapping = (upnp_igd_port_mapping *) arg;
201 port_mapping = (UpnpPortBinding*) mapping->cookie;
202 port_mapping->state = LinphoneUpnpStateIdle;
203 upnp_port_binding_log(ORTP_MESSAGE, "Removed port binding", port_mapping);
204 upnp_config_remove_port_binding(lupnp, port_mapping);
206 lupnp->pending_bindings = ms_list_remove(lupnp->pending_bindings, port_mapping);
207 upnp_port_binding_release(port_mapping);
210 case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE:
211 mapping = (upnp_igd_port_mapping *) arg;
212 port_mapping = (UpnpPortBinding*) mapping->cookie;
213 if(upnp_context_send_remove_port_binding(lupnp, port_mapping) != 0) {
214 upnp_port_binding_log(ORTP_ERROR, "Can't remove port binding", port_mapping);
215 upnp_config_remove_port_binding(lupnp, port_mapping);
218 lupnp->pending_bindings = ms_list_remove(lupnp->pending_bindings, port_mapping);
219 upnp_port_binding_release(port_mapping);
226 if(lupnp->pending_bindings == NULL) {
227 if(lupnp->cleaning == TRUE) {
228 lupnp->emit = TRUE; // Emit port bindings
229 lupnp->cleaning = FALSE;
231 pthread_cond_signal(&lupnp->cond);
233 ms_mutex_unlock(&lupnp->mutex);
241 UpnpContext* 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->cond, NULL);
250 lupnp->pending_bindings = NULL;
251 lupnp->adding_configs = NULL;
252 lupnp->removing_configs = NULL;
253 lupnp->clean = FALSE;
254 lupnp->cleaning = FALSE;
256 lupnp->state = LinphoneUpnpStateIdle;
257 ms_message("uPnP IGD: New %p for core %p", lupnp, lc);
259 linphone_core_get_sip_transports(lc, &transport);
260 if(transport.udp_port != 0) {
261 lupnp->sip_udp = upnp_port_binding_new();
262 lupnp->sip_udp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
263 lupnp->sip_udp->local_port = transport.udp_port;
264 lupnp->sip_udp->external_port = transport.udp_port;
266 lupnp->sip_udp = NULL;
268 if(transport.tcp_port != 0) {
269 lupnp->sip_tcp = upnp_port_binding_new();
270 lupnp->sip_tcp->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
271 lupnp->sip_tcp->local_port = transport.tcp_port;
272 lupnp->sip_tcp->external_port = transport.tcp_port;
274 lupnp->sip_tcp = NULL;
276 if(transport.tls_port != 0) {
277 lupnp->sip_tls = upnp_port_binding_new();
278 lupnp->sip_tls->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
279 lupnp->sip_tls->local_port = transport.tls_port;
280 lupnp->sip_tls->external_port = transport.tls_port;
282 lupnp->sip_tls = NULL;
285 linphone_core_add_iterate_hook(lc, linphone_core_upnp_hook, lupnp);
287 lupnp->upnp_igd_ctxt = NULL;
288 lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, lupnp);
289 if(lupnp->upnp_igd_ctxt == NULL) {
290 lupnp->state = LinphoneUpnpStateKo;
291 ms_error("Can't create uPnP IGD context");
295 ip_address = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
296 if(lupnp->sip_udp != NULL) {
297 strncpy(lupnp->sip_udp->local_addr, ip_address, sizeof(lupnp->sip_udp->local_addr));
299 if(lupnp->sip_tcp != NULL) {
300 strncpy(lupnp->sip_tcp->local_addr, ip_address, sizeof(lupnp->sip_tcp->local_addr));
302 if(lupnp->sip_tls != NULL) {
303 strncpy(lupnp->sip_tls->local_addr, ip_address, sizeof(lupnp->sip_tls->local_addr));
306 lupnp->state = LinphoneUpnpStatePending;
310 void upnp_context_destroy(UpnpContext *lupnp) {
312 * Not need, all hooks are removed before
313 * linphone_core_remove_iterate_hook(lc, linphone_core_upnp_hook, lc);
316 /* Send port binding removes */
317 if(lupnp->sip_udp != NULL) {
318 upnp_context_send_remove_port_binding(lupnp, lupnp->sip_udp);
319 lupnp->sip_udp = NULL;
321 if(lupnp->sip_tcp != NULL) {
322 upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tcp);
323 lupnp->sip_tcp = NULL;
325 if(lupnp->sip_tls != NULL) {
326 upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tls);
327 lupnp->sip_tcp = NULL;
330 /* Wait all pending bindings are done */
331 ms_message("uPnP IGD: Wait all pending port bindings ...");
332 ms_mutex_lock(&lupnp->mutex);
333 ms_cond_wait(&lupnp->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 upnp_port_binding_release(lupnp->sip_udp);
346 lupnp->sip_udp = NULL;
348 if(lupnp->sip_tcp != NULL) {
349 upnp_port_binding_release(lupnp->sip_tcp);
350 lupnp->sip_tcp = NULL;
352 if(lupnp->sip_tls != NULL) {
353 upnp_port_binding_release(lupnp->sip_tls);
354 lupnp->sip_tcp = NULL;
358 ms_list_for_each(lupnp->adding_configs,(void (*)(void*))upnp_port_binding_release);
359 lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
360 ms_list_for_each(lupnp->removing_configs,(void (*)(void*))upnp_port_binding_release);
361 lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
362 ms_list_for_each(lupnp->pending_bindings,(void (*)(void*))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->cond);
368 ms_message("uPnP IGD: destroy %p", lupnp);
372 UpnpState upnp_context_get_state(UpnpContext *ctx) {
376 const char* upnp_context_get_external_ipaddress(UpnpContext *ctx) {
377 return upnp_igd_get_external_ipaddress(ctx->upnp_igd_ctxt);
380 int upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port) {
381 upnp_igd_port_mapping mapping;
382 char description[128];
384 if(port->state == LinphoneUpnpStateIdle) {
386 port->state = LinphoneUpnpStateAdding;
387 } else if(port->state != LinphoneUpnpStateAdding) {
388 ms_error("uPnP: try to add a port binding in wrong state: %d", port->state);
392 if(port->retry >= UPNP_ADD_MAX_RETRY) {
395 mapping.cookie = upnp_port_binding_retain(port);
396 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
398 mapping.local_port = port->local_port;
399 mapping.local_host = port->local_addr;
400 if(port->external_port == -1)
401 mapping.remote_port = rand()%(0xffff - 1024) + 1024;
403 mapping.remote_port = port->external_port;
404 mapping.remote_host = "";
405 snprintf(description, 128, "%s %s at %s:%d",
407 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP": "UDP",
408 port->local_addr, port->local_port);
409 mapping.description = description;
410 mapping.protocol = port->protocol;
413 upnp_port_binding_log(ORTP_DEBUG, "Adding port binding...", port);
414 ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
417 port->state = LinphoneUpnpStateKo;
422 int upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port) {
423 upnp_igd_port_mapping mapping;
425 if(port->state == LinphoneUpnpStateOk) {
427 port->state = LinphoneUpnpStateRemoving;
428 } else if(port->state != LinphoneUpnpStateRemoving) {
429 ms_error("uPnP: try to remove a port binding in wrong state: %d", port->state);
433 if(port->retry >= UPNP_REMOVE_MAX_RETRY) {
436 mapping.cookie = upnp_port_binding_retain(port);
437 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
439 mapping.remote_port = port->external_port;
440 mapping.remote_host = "";
441 mapping.protocol = port->protocol;
443 upnp_port_binding_log(ORTP_DEBUG, "Removing port binding...", port);
444 ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
447 port->state = LinphoneUpnpStateKo;
453 * uPnP Core interfaces
456 int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool_t video) {
457 LinphoneCore *lc = call->core;
458 UpnpContext *lupnp = lc->upnp;
460 const char *local_addr, *external_addr;
466 ms_mutex_lock(&lupnp->mutex);
467 // Don't handle when the call
468 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
470 local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
471 external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
476 strncpy(call->upnp_session->audio->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
477 strncpy(call->upnp_session->audio->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
478 call->upnp_session->audio->rtp->local_port = call->audio_port;
479 if(call->upnp_session->audio->rtp->external_port == -1) {
480 call->upnp_session->audio->rtp->external_port = call->audio_port;
482 strncpy(call->upnp_session->audio->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
483 strncpy(call->upnp_session->audio->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
484 call->upnp_session->audio->rtcp->local_port = call->audio_port+1;
485 if(call->upnp_session->audio->rtcp->external_port == -1) {
486 call->upnp_session->audio->rtcp->external_port = call->audio_port+1;
488 if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle && audio) {
489 // Add audio port binding
490 upnp_context_send_add_port_binding(lupnp, call->upnp_session->audio->rtp);
491 } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk && !audio) {
492 // Remove audio port binding
493 upnp_context_send_remove_port_binding(lupnp, call->upnp_session->audio->rtp);
495 if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle && audio) {
496 // Add audio port binding
497 upnp_context_send_add_port_binding(lupnp, call->upnp_session->audio->rtcp);
498 } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk && !audio) {
499 // Remove audio port binding
500 upnp_context_send_remove_port_binding(lupnp, call->upnp_session->audio->rtcp);
506 strncpy(call->upnp_session->video->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
507 strncpy(call->upnp_session->video->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
508 call->upnp_session->video->rtp->local_port = call->video_port;
509 if(call->upnp_session->video->rtp->external_port == -1) {
510 call->upnp_session->video->rtp->external_port = call->video_port;
512 strncpy(call->upnp_session->video->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
513 strncpy(call->upnp_session->video->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
514 call->upnp_session->video->rtcp->local_port = call->video_port+1;
515 if(call->upnp_session->video->rtcp->external_port == -1) {
516 call->upnp_session->video->rtcp->external_port = call->video_port+1;
518 if(call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle && video) {
519 // Add video port binding
520 upnp_context_send_add_port_binding(lupnp, call->upnp_session->video->rtp);
521 } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateOk && !video) {
522 // Remove video port binding
523 upnp_context_send_remove_port_binding(lupnp, call->upnp_session->video->rtp);
525 if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle && video) {
526 // Add video port binding
527 upnp_context_send_add_port_binding(lupnp, call->upnp_session->video->rtcp);
528 } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk && !video) {
529 // Remove video port binding
530 upnp_context_send_remove_port_binding(lupnp, call->upnp_session->video->rtcp);
534 ms_mutex_unlock(&lupnp->mutex);
537 * Update uPnP call state
539 upnp_call_process(call);
545 int linphone_core_update_upnp_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) {
546 bool_t audio = FALSE;
547 bool_t video = FALSE;
549 const SalStreamDescription *stream;
551 for (i = 0; i < md->nstreams; i++) {
552 stream = &md->streams[i];
553 if(stream->type == SalAudio) {
555 } else if(stream->type == SalVideo) {
560 return linphone_core_update_upnp_audio_video(call, audio, video);
563 int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
564 return linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
567 int upnp_call_process(LinphoneCall *call) {
568 LinphoneCore *lc = call->core;
569 UpnpContext *lupnp = lc->upnp;
577 ms_mutex_lock(&lupnp->mutex);
579 // Don't handle when the call
580 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
586 if((call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle) &&
587 (call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle)) {
588 call->upnp_session->audio->state = LinphoneUpnpStateOk;
589 } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateAdding ||
590 call->upnp_session->audio->rtp->state == LinphoneUpnpStateRemoving ||
591 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateAdding ||
592 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateRemoving) {
593 call->upnp_session->audio->state = LinphoneUpnpStatePending;
594 } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateKo ||
595 call->upnp_session->audio->rtp->state == LinphoneUpnpStateKo) {
596 call->upnp_session->audio->state = LinphoneUpnpStateKo;
598 call->upnp_session->audio->state = LinphoneUpnpStateIdle;
604 if((call->upnp_session->video->rtp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle) &&
605 (call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle)) {
606 call->upnp_session->video->state = LinphoneUpnpStateOk;
607 } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateAdding ||
608 call->upnp_session->video->rtp->state == LinphoneUpnpStateRemoving ||
609 call->upnp_session->video->rtcp->state == LinphoneUpnpStateAdding ||
610 call->upnp_session->video->rtcp->state == LinphoneUpnpStateRemoving) {
611 call->upnp_session->video->state = LinphoneUpnpStatePending;
612 } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateKo ||
613 call->upnp_session->video->rtp->state == LinphoneUpnpStateKo) {
614 call->upnp_session->video->state = LinphoneUpnpStateKo;
616 call->upnp_session->video->state = LinphoneUpnpStateIdle;
620 * Update session state
622 oldState = call->upnp_session->state;
623 if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
624 call->upnp_session->video->state == LinphoneUpnpStateOk) {
625 call->upnp_session->state = LinphoneUpnpStateOk;
626 } else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
627 call->upnp_session->video->state == LinphoneUpnpStatePending) {
628 call->upnp_session->state = LinphoneUpnpStatePending;
629 } else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
630 call->upnp_session->video->state == LinphoneUpnpStateKo) {
631 call->upnp_session->state = LinphoneUpnpStateKo;
633 call->upnp_session->state = LinphoneUpnpStateIdle;
636 /* When change is done proceed update */
637 if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
638 (call->upnp_session->state == LinphoneUpnpStateOk || call->upnp_session->state == LinphoneUpnpStateKo)) {
639 if(call->upnp_session->state == LinphoneUpnpStateOk)
640 ms_message("uPnP IGD: uPnP for Call %p is ok", call);
642 ms_message("uPnP IGD: uPnP for Call %p is ko", call);
644 switch (call->state) {
645 case LinphoneCallUpdating:
646 linphone_core_start_update_call(lc, call);
648 case LinphoneCallUpdatedByRemote:
649 linphone_core_start_accept_call_update(lc, call);
651 case LinphoneCallOutgoingInit:
652 linphone_core_proceed_with_invite_if_ready(lc, call, NULL);
654 case LinphoneCallIdle:
655 linphone_core_notify_incoming_call(lc, call);
663 ms_mutex_unlock(&lupnp->mutex);
667 bool_t linphone_core_upnp_hook(void *data) {
671 UpnpPortBinding *port_mapping;
672 UpnpContext *lupnp = (UpnpContext *)data;
674 ms_mutex_lock(&lupnp->mutex);
676 if(lupnp->clean && !lupnp->cleaning) {
677 ms_message("uPnP IGD: Clean port mappings");
678 lupnp->clean = FALSE;
679 // Remove old mapping
680 list = upnp_config_list_port_bindings(lupnp->lc->config);
684 lupnp->cleaning = TRUE;
685 for(item = list;item != NULL; item = item->next) {
686 port_mapping = (UpnpPortBinding *)item->data;
687 upnp_context_send_remove_port_binding(lupnp, port_mapping);
689 ms_list_for_each(list,(void (*)(void*))upnp_port_binding_release);
690 list = ms_list_free(list);
695 ms_message("uPnP IGD: Update port mappings");
698 /* Force port bindings */
699 if(lupnp->sip_udp != NULL) {
700 lupnp->sip_udp->state = LinphoneUpnpStateIdle;
701 upnp_context_send_add_port_binding(lupnp, lupnp->sip_udp);
703 if(lupnp->sip_tcp != NULL) {
704 lupnp->sip_udp->state = LinphoneUpnpStateIdle;
705 upnp_context_send_add_port_binding(lupnp, lupnp->sip_tcp);
707 if(lupnp->sip_tls != NULL) {
708 lupnp->sip_udp->state = LinphoneUpnpStateIdle;
709 upnp_context_send_add_port_binding(lupnp, lupnp->sip_tls);
711 list = lupnp->lc->calls;
712 while(list != NULL) {
713 call = (LinphoneCall *)list->data;
714 call->upnp_session->audio->rtp->state = LinphoneUpnpStateIdle;
715 call->upnp_session->audio->rtcp->state = LinphoneUpnpStateIdle;
716 call->upnp_session->video->rtp->state = LinphoneUpnpStateIdle;
717 call->upnp_session->video->rtcp->state = LinphoneUpnpStateIdle;
718 linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
724 for(item = lupnp->adding_configs;item!=NULL;item=item->next) {
725 port_mapping = (UpnpPortBinding *)item->data;
726 snprintf(key, sizeof(key), "%s-%d-%d",
727 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
728 port_mapping->external_port,
729 port_mapping->local_port);
730 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, "uPnP");
731 upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping);
733 ms_list_for_each(lupnp->adding_configs,(void (*)(void*))upnp_port_binding_release);
734 lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
737 for(item = lupnp->removing_configs;item!=NULL;item=item->next) {
738 port_mapping = (UpnpPortBinding *)item->data;
739 snprintf(key, sizeof(key), "%s-%d-%d",
740 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
741 port_mapping->external_port,
742 port_mapping->local_port);
743 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, NULL);
744 upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping);
746 ms_list_for_each(lupnp->removing_configs,(void (*)(void*))upnp_port_binding_release);
747 lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
749 ms_mutex_unlock(&lupnp->mutex);
753 int linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
755 SalStreamDescription *stream;
756 UpnpStream *upnpStream;
758 for (i = 0; i < desc->nstreams; i++) {
759 stream = &desc->streams[i];
761 if(stream->type == SalAudio) {
762 upnpStream = session->audio;
763 } else if(stream->type == SalVideo) {
764 upnpStream = session->video;
766 if(upnpStream != NULL) {
767 if(upnpStream->rtp->state == LinphoneUpnpStateOk) {
768 strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
769 stream->rtp_port = upnpStream->rtp->external_port;
771 if(upnpStream->rtcp->state == LinphoneUpnpStateOk) {
772 strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
773 stream->rtcp_port = upnpStream->rtcp->external_port;
785 UpnpPortBinding *upnp_port_binding_new() {
786 UpnpPortBinding *port = NULL;
787 port = ms_new0(UpnpPortBinding,1);
788 ms_mutex_init(&port->mutex, NULL);
789 port->state = LinphoneUpnpStateIdle;
790 port->local_addr[0] = '\0';
791 port->local_port = -1;
792 port->external_addr[0] = '\0';
793 port->external_port = -1;
798 UpnpPortBinding *upnp_port_binding_copy(const UpnpPortBinding *port) {
799 UpnpPortBinding *new_port = NULL;
800 new_port = ms_new0(UpnpPortBinding,1);
801 memcpy(new_port, port, sizeof(UpnpPortBinding));
802 ms_mutex_init(&new_port->mutex, NULL);
807 void upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port) {
808 if(strlen(port->local_addr)) {
809 ortp_log(level, "uPnP IGD: %s %s|%d->%s:%d", msg,
810 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
815 ortp_log(level, "uPnP IGD: %s %s|%d->%d", msg,
816 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
822 bool_t upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
823 return port1->protocol == port2->protocol &&
824 port1->local_port == port2->local_port &&
825 port1->external_port == port2->external_port;
828 UpnpPortBinding *upnp_port_binding_retain(UpnpPortBinding *port) {
829 ms_mutex_lock(&port->mutex);
831 ms_mutex_unlock(&port->mutex);
835 void upnp_port_binding_release(UpnpPortBinding *port) {
836 ms_mutex_lock(&port->mutex);
837 if(--port->ref == 0) {
838 ms_mutex_unlock(&port->mutex);
839 ms_mutex_destroy(&port->mutex);
843 ms_mutex_unlock(&port->mutex);
851 UpnpStream* upnp_stream_new() {
852 UpnpStream *stream = ms_new0(UpnpStream,1);
853 stream->state = LinphoneUpnpStateIdle;
854 stream->rtp = upnp_port_binding_new();
855 stream->rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
856 stream->rtcp = upnp_port_binding_new();
857 stream->rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
861 void upnp_stream_destroy(UpnpStream* stream) {
862 upnp_port_binding_release(stream->rtp);
864 upnp_port_binding_release(stream->rtcp);
874 UpnpSession* upnp_session_new(LinphoneCall* call) {
875 UpnpSession *session = ms_new0(UpnpSession,1);
876 session->call = call;
877 session->state = LinphoneUpnpStateIdle;
878 session->audio = upnp_stream_new();
879 session->video = upnp_stream_new();
883 void upnp_session_destroy(UpnpSession *session) {
884 LinphoneCore *lc = session->call->core;
886 if(lc->upnp != NULL) {
887 /* Remove bindings */
888 if(session->audio->rtp->state != LinphoneUpnpStateKo && session->audio->rtp->state != LinphoneUpnpStateIdle) {
889 upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtp);
891 if(session->audio->rtcp->state != LinphoneUpnpStateKo && session->audio->rtcp->state != LinphoneUpnpStateIdle) {
892 upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtcp);
894 if(session->video->rtp->state != LinphoneUpnpStateKo && session->video->rtp->state != LinphoneUpnpStateIdle) {
895 upnp_context_send_remove_port_binding(lc->upnp, session->video->rtp);
897 if(session->video->rtcp->state != LinphoneUpnpStateKo && session->video->rtcp->state != LinphoneUpnpStateIdle) {
898 upnp_context_send_remove_port_binding(lc->upnp, session->video->rtcp);
902 upnp_stream_destroy(session->audio);
903 upnp_stream_destroy(session->video);
907 UpnpState upnp_session_get_state(UpnpSession *session) {
908 return session->state;
915 MSList *upnp_config_list_port_bindings(struct _LpConfig *lpc) {
916 char protocol_str[4]; // TCP or UDP
917 upnp_igd_ip_protocol protocol;
920 MSList *retList = NULL;
921 UpnpPortBinding *port;
925 LpSection *sec=lp_config_find_section(lpc, UPNP_SECTION_NAME);
930 while(elem != NULL) {
931 item=(LpItem*)elem->data;
933 if(sscanf(item->key, "%3s-%i-%i", protocol_str, &external_port, &local_port) == 3) {
934 if(strcasecmp(protocol_str, "TCP") == 0) {
935 protocol = UPNP_IGD_IP_PROTOCOL_TCP;
936 } else if(strcasecmp(protocol_str, "UDP") == 0) {
937 protocol = UPNP_IGD_IP_PROTOCOL_UDP;
942 port = upnp_port_binding_new();
943 port->state = LinphoneUpnpStateOk;
944 port->protocol = protocol;
945 port->external_port = external_port;
946 port->local_port = local_port;
947 retList = ms_list_append(retList, port);
952 elem = ms_list_next(elem);
954 ms_warning("uPnP configuration invalid line: %s", item->key);
955 lp_section_remove_item(sec, item);
962 void upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
964 UpnpPortBinding *list_port;
966 list = lupnp->removing_configs;
967 while(list != NULL) {
968 list_port = (UpnpPortBinding *)list->data;
969 if(upnp_port_binding_equal(list_port, port) == TRUE) {
970 lupnp->removing_configs = ms_list_remove(lupnp->removing_configs, list_port);
971 upnp_port_binding_release(list_port);
974 list = ms_list_next(list);
977 list = lupnp->adding_configs;
978 while(list != NULL) {
979 list_port = (UpnpPortBinding *)list->data;
980 if(upnp_port_binding_equal(list_port, port) == TRUE) {
983 list = ms_list_next(list);
986 list_port = upnp_port_binding_copy(port);
987 lupnp->adding_configs = ms_list_append(lupnp->adding_configs, list_port);
990 void upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
992 UpnpPortBinding *list_port;
994 list = lupnp->adding_configs;
995 while(list != NULL) {
996 list_port = (UpnpPortBinding *)list->data;
997 if(upnp_port_binding_equal(list_port, port) == TRUE) {
998 lupnp->adding_configs = ms_list_remove(lupnp->adding_configs, list_port);
999 upnp_port_binding_release(list_port);
1002 list = ms_list_next(list);
1005 list = lupnp->removing_configs;
1006 while(list != NULL) {
1007 list_port = (UpnpPortBinding *)list->data;
1008 if(upnp_port_binding_equal(list_port, port) == TRUE) {
1011 list = ms_list_next(list);
1014 list_port = upnp_port_binding_copy(port);
1015 lupnp->removing_configs = ms_list_append(lupnp->removing_configs, list_port);