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 *lupnp) {
372 LinphoneUpnpState state;
373 ms_mutex_lock(&lupnp->mutex);
374 state = lupnp->state;
375 ms_mutex_unlock(&lupnp->mutex);
379 int linphone_upnp_context_get_external_port(UpnpContext *lupnp) {
381 ms_mutex_lock(&lupnp->mutex);
383 /* Send port binding removes */
384 if(lupnp->sip_udp != NULL) {
385 if(lupnp->sip_udp->state == LinphoneUpnpStateOk)
386 port = lupnp->sip_udp->external_port;
387 } else if(lupnp->sip_tcp != NULL) {
388 if(lupnp->sip_tcp->state == LinphoneUpnpStateOk)
389 port = lupnp->sip_tcp->external_port;
390 } else if(lupnp->sip_tls != NULL) {
391 if(lupnp->sip_tls->state == LinphoneUpnpStateOk)
392 port = lupnp->sip_tls->external_port;
395 ms_mutex_unlock(&lupnp->mutex);
399 const char* linphone_upnp_context_get_external_ipaddress(UpnpContext *ctx) {
400 return upnp_igd_get_external_ipaddress(ctx->upnp_igd_ctxt);
403 int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port) {
404 upnp_igd_port_mapping mapping;
405 char description[128];
408 if(lupnp->state != LinphoneUpnpStateOk) {
412 // Compute port binding state
413 if(port->state != LinphoneUpnpStateAdding) {
414 port->to_remove = FALSE;
415 switch(port->state) {
416 case LinphoneUpnpStateKo:
417 case LinphoneUpnpStateIdle: {
419 port->state = LinphoneUpnpStateAdding;
422 case LinphoneUpnpStateRemoving: {
432 if(port->retry >= UPNP_ADD_MAX_RETRY) {
435 mapping.cookie = linphone_upnp_port_binding_retain(port);
436 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
438 mapping.local_port = port->local_port;
439 mapping.local_host = port->local_addr;
440 if(port->external_port == -1)
441 mapping.remote_port = rand()%(0xffff - 1024) + 1024;
443 mapping.remote_port = port->external_port;
444 mapping.remote_host = "";
445 snprintf(description, 128, "%s %s at %s:%d",
447 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP": "UDP",
448 port->local_addr, port->local_port);
449 mapping.description = description;
450 mapping.protocol = port->protocol;
453 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to add port binding", port);
454 ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
457 port->state = LinphoneUpnpStateKo;
462 int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port) {
463 upnp_igd_port_mapping mapping;
466 if(lupnp->state != LinphoneUpnpStateOk) {
470 // Compute port binding state
471 if(port->state != LinphoneUpnpStateRemoving) {
472 port->to_add = FALSE;
473 switch(port->state) {
474 case LinphoneUpnpStateOk: {
476 port->state = LinphoneUpnpStateRemoving;
479 case LinphoneUpnpStateAdding: {
480 port->to_remove = TRUE;
489 if(port->retry >= UPNP_REMOVE_MAX_RETRY) {
492 mapping.cookie = linphone_upnp_port_binding_retain(port);
493 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
495 mapping.remote_port = port->external_port;
496 mapping.remote_host = "";
497 mapping.protocol = port->protocol;
499 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to remove port binding", port);
500 ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
503 port->state = LinphoneUpnpStateKo;
509 * uPnP Core interfaces
512 int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool_t video) {
513 LinphoneCore *lc = call->core;
514 UpnpContext *lupnp = lc->upnp;
516 const char *local_addr, *external_addr;
522 ms_mutex_lock(&lupnp->mutex);
523 // Don't handle when the call
524 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
526 local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
527 external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
532 strncpy(call->upnp_session->audio->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
533 strncpy(call->upnp_session->audio->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
534 call->upnp_session->audio->rtp->local_port = call->audio_port;
535 if(call->upnp_session->audio->rtp->external_port == -1) {
536 call->upnp_session->audio->rtp->external_port = call->audio_port;
538 strncpy(call->upnp_session->audio->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
539 strncpy(call->upnp_session->audio->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
540 call->upnp_session->audio->rtcp->local_port = call->audio_port+1;
541 if(call->upnp_session->audio->rtcp->external_port == -1) {
542 call->upnp_session->audio->rtcp->external_port = call->audio_port+1;
545 // Add audio port binding
546 linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->audio->rtp);
547 linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->audio->rtcp);
549 // Remove audio port binding
550 linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->audio->rtp);
551 linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->audio->rtcp);
557 strncpy(call->upnp_session->video->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
558 strncpy(call->upnp_session->video->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
559 call->upnp_session->video->rtp->local_port = call->video_port;
560 if(call->upnp_session->video->rtp->external_port == -1) {
561 call->upnp_session->video->rtp->external_port = call->video_port;
563 strncpy(call->upnp_session->video->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
564 strncpy(call->upnp_session->video->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
565 call->upnp_session->video->rtcp->local_port = call->video_port+1;
566 if(call->upnp_session->video->rtcp->external_port == -1) {
567 call->upnp_session->video->rtcp->external_port = call->video_port+1;
570 // Add video port binding
571 linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->video->rtp);
572 linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->video->rtcp);
574 // Remove video port binding
575 linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->video->rtp);
576 linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->video->rtcp);
580 ms_mutex_unlock(&lupnp->mutex);
583 * Update uPnP call state
585 linphone_upnp_call_process(call);
591 int linphone_core_update_upnp_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) {
592 bool_t audio = FALSE;
593 bool_t video = FALSE;
595 const SalStreamDescription *stream;
597 for (i = 0; i < md->n_total_streams; i++) {
598 stream = &md->streams[i];
599 if(stream->type == SalAudio) {
601 } else if(stream->type == SalVideo) {
606 return linphone_core_update_upnp_audio_video(call, audio, video);
609 int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
610 return linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
613 void linphone_core_update_upnp_state_in_call_stats(LinphoneCall *call) {
614 call->stats[LINPHONE_CALL_STATS_AUDIO].upnp_state = call->upnp_session->audio->state;
615 call->stats[LINPHONE_CALL_STATS_VIDEO].upnp_state = call->upnp_session->video->state;
618 int linphone_upnp_call_process(LinphoneCall *call) {
619 LinphoneCore *lc = call->core;
620 UpnpContext *lupnp = lc->upnp;
622 LinphoneUpnpState oldState = 0, newState = 0;
628 ms_mutex_lock(&lupnp->mutex);
630 // Don't handle when the call
631 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
637 if((call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle) &&
638 (call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle)) {
639 call->upnp_session->audio->state = LinphoneUpnpStateOk;
640 } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateAdding ||
641 call->upnp_session->audio->rtp->state == LinphoneUpnpStateRemoving ||
642 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateAdding ||
643 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateRemoving) {
644 call->upnp_session->audio->state = LinphoneUpnpStatePending;
645 } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateKo ||
646 call->upnp_session->audio->rtp->state == LinphoneUpnpStateKo) {
647 call->upnp_session->audio->state = LinphoneUpnpStateKo;
649 call->upnp_session->audio->state = LinphoneUpnpStateIdle;
655 if((call->upnp_session->video->rtp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle) &&
656 (call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle)) {
657 call->upnp_session->video->state = LinphoneUpnpStateOk;
658 } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateAdding ||
659 call->upnp_session->video->rtp->state == LinphoneUpnpStateRemoving ||
660 call->upnp_session->video->rtcp->state == LinphoneUpnpStateAdding ||
661 call->upnp_session->video->rtcp->state == LinphoneUpnpStateRemoving) {
662 call->upnp_session->video->state = LinphoneUpnpStatePending;
663 } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateKo ||
664 call->upnp_session->video->rtp->state == LinphoneUpnpStateKo) {
665 call->upnp_session->video->state = LinphoneUpnpStateKo;
667 call->upnp_session->video->state = LinphoneUpnpStateIdle;
671 * Update session state
673 oldState = call->upnp_session->state;
674 if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
675 call->upnp_session->video->state == LinphoneUpnpStateOk) {
676 call->upnp_session->state = LinphoneUpnpStateOk;
677 } else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
678 call->upnp_session->video->state == LinphoneUpnpStatePending) {
679 call->upnp_session->state = LinphoneUpnpStatePending;
680 } else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
681 call->upnp_session->video->state == LinphoneUpnpStateKo) {
682 call->upnp_session->state = LinphoneUpnpStateKo;
684 call->upnp_session->state = LinphoneUpnpStateIdle;
686 newState = call->upnp_session->state;
688 /* When change is done proceed update */
689 if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
690 (call->upnp_session->state == LinphoneUpnpStateOk || call->upnp_session->state == LinphoneUpnpStateKo)) {
691 if(call->upnp_session->state == LinphoneUpnpStateOk)
692 ms_message("uPnP IGD: uPnP for Call %p is ok", call);
694 ms_message("uPnP IGD: uPnP for Call %p is ko", call);
696 switch (call->state) {
697 case LinphoneCallUpdating:
698 linphone_core_start_update_call(lc, call);
700 case LinphoneCallUpdatedByRemote:
701 linphone_core_start_accept_call_update(lc, call);
703 case LinphoneCallOutgoingInit:
704 linphone_core_proceed_with_invite_if_ready(lc, call, NULL);
706 case LinphoneCallIdle:
707 linphone_core_notify_incoming_call(lc, call);
715 ms_mutex_unlock(&lupnp->mutex);
718 * Update uPnP call stats
720 if(oldState != newState) {
721 linphone_core_update_upnp_state_in_call_stats(call);
727 void linphone_core_upnp_refresh(UpnpContext *lupnp) {
728 MSList *global_list = NULL;
732 UpnpPortBinding *port_mapping, *port_mapping2;
734 ms_message("uPnP IGD: Refresh mappings");
736 /* Remove context port bindings */
737 if(lupnp->sip_udp != NULL) {
738 global_list = ms_list_append(global_list, lupnp->sip_udp);
740 if(lupnp->sip_tcp != NULL) {
741 global_list = ms_list_append(global_list, lupnp->sip_tcp);
743 if(lupnp->sip_tls != NULL) {
744 global_list = ms_list_append(global_list, lupnp->sip_tls);
747 /* Remove call port bindings */
748 list = lupnp->lc->calls;
749 while(list != NULL) {
750 call = (LinphoneCall *)list->data;
751 if(call->upnp_session != NULL) {
752 global_list = ms_list_append(global_list, call->upnp_session->audio->rtp);
753 global_list = ms_list_append(global_list, call->upnp_session->audio->rtcp);
754 global_list = ms_list_append(global_list, call->upnp_session->video->rtp);
755 global_list = ms_list_append(global_list, call->upnp_session->video->rtcp);
760 // Remove port binding configurations
761 list = linphone_upnp_config_list_port_bindings(lupnp->lc->config);
762 for(item = list;item != NULL; item = item->next) {
763 port_mapping = (UpnpPortBinding *)item->data;
764 port_mapping2 = linphone_upnp_port_binding_equivalent_in_list(global_list, port_mapping);
765 if(port_mapping2 == NULL) {
766 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping);
767 } else if(port_mapping2->state == LinphoneUpnpStateIdle){
768 /* Force to remove */
769 port_mapping2->state = LinphoneUpnpStateOk;
772 ms_list_for_each(list, (void (*)(void*))linphone_upnp_port_binding_release);
773 list = ms_list_free(list);
776 // (Re)Add removed port bindings
778 while(list != NULL) {
779 port_mapping = (UpnpPortBinding *)list->data;
780 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping);
781 linphone_upnp_context_send_add_port_binding(lupnp, port_mapping);
784 global_list = ms_list_free(global_list);
787 bool_t linphone_core_upnp_hook(void *data) {
790 UpnpPortBinding *port_mapping;
791 UpnpContext *lupnp = (UpnpContext *)data;
792 ms_mutex_lock(&lupnp->mutex);
795 for(item = lupnp->adding_configs;item!=NULL;item=item->next) {
796 port_mapping = (UpnpPortBinding *)item->data;
797 snprintf(key, sizeof(key), "%s-%d-%d",
798 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
799 port_mapping->external_port,
800 port_mapping->local_port);
801 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, "uPnP");
802 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping);
804 ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
805 lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
808 for(item = lupnp->removing_configs;item!=NULL;item=item->next) {
809 port_mapping = (UpnpPortBinding *)item->data;
810 snprintf(key, sizeof(key), "%s-%d-%d",
811 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
812 port_mapping->external_port,
813 port_mapping->local_port);
814 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, NULL);
815 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping);
817 ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
818 lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
820 ms_mutex_unlock(&lupnp->mutex);
824 int linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
826 SalStreamDescription *stream;
827 UpnpStream *upnpStream;
829 for (i = 0; i < desc->n_active_streams; i++) {
830 stream = &desc->streams[i];
832 if(stream->type == SalAudio) {
833 upnpStream = session->audio;
834 } else if(stream->type == SalVideo) {
835 upnpStream = session->video;
837 if(upnpStream != NULL) {
838 if(upnpStream->rtp->state == LinphoneUpnpStateOk) {
839 strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
840 stream->rtp_port = upnpStream->rtp->external_port;
842 if(upnpStream->rtcp->state == LinphoneUpnpStateOk) {
843 strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
844 stream->rtcp_port = upnpStream->rtcp->external_port;
856 UpnpPortBinding *linphone_upnp_port_binding_new() {
857 UpnpPortBinding *port = NULL;
858 port = ms_new0(UpnpPortBinding,1);
859 ms_mutex_init(&port->mutex, NULL);
860 port->state = LinphoneUpnpStateIdle;
861 port->local_addr[0] = '\0';
862 port->local_port = -1;
863 port->external_addr[0] = '\0';
864 port->external_port = -1;
865 port->to_remove = FALSE;
866 port->to_add = FALSE;
871 UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port) {
872 UpnpPortBinding *new_port = NULL;
873 new_port = ms_new0(UpnpPortBinding,1);
874 memcpy(new_port, port, sizeof(UpnpPortBinding));
875 ms_mutex_init(&new_port->mutex, NULL);
880 void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port) {
881 if(strlen(port->local_addr)) {
882 ortp_log(level, "uPnP IGD: %s %s|%d->%s:%d (retry %d)", msg,
883 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
889 ortp_log(level, "uPnP IGD: %s %s|%d->%d (retry %d)", msg,
890 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
897 bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
898 return port1->protocol == port2->protocol &&
899 port1->local_port == port2->local_port &&
900 port1->external_port == port2->external_port;
903 UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port) {
904 UpnpPortBinding *port_mapping;
905 while(list != NULL) {
906 port_mapping = (UpnpPortBinding *)list->data;
907 if(linphone_upnp_port_binding_equal(port, port_mapping)) {
916 UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port) {
917 ms_mutex_lock(&port->mutex);
919 ms_mutex_unlock(&port->mutex);
923 void linphone_upnp_port_binding_release(UpnpPortBinding *port) {
924 ms_mutex_lock(&port->mutex);
925 if(--port->ref == 0) {
926 ms_mutex_unlock(&port->mutex);
927 ms_mutex_destroy(&port->mutex);
931 ms_mutex_unlock(&port->mutex);
939 UpnpStream* linphone_upnp_stream_new() {
940 UpnpStream *stream = ms_new0(UpnpStream,1);
941 stream->state = LinphoneUpnpStateIdle;
942 stream->rtp = linphone_upnp_port_binding_new();
943 stream->rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
944 stream->rtcp = linphone_upnp_port_binding_new();
945 stream->rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
949 void linphone_upnp_stream_destroy(UpnpStream* stream) {
950 linphone_upnp_port_binding_release(stream->rtp);
952 linphone_upnp_port_binding_release(stream->rtcp);
962 UpnpSession* linphone_upnp_session_new(LinphoneCall* call) {
963 UpnpSession *session = ms_new0(UpnpSession,1);
964 session->call = call;
965 session->state = LinphoneUpnpStateIdle;
966 session->audio = linphone_upnp_stream_new();
967 session->video = linphone_upnp_stream_new();
971 void linphone_upnp_session_destroy(UpnpSession *session) {
972 LinphoneCore *lc = session->call->core;
974 if(lc->upnp != NULL) {
975 /* Remove bindings */
976 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtp);
977 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtcp);
978 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtp);
979 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtcp);
982 linphone_upnp_stream_destroy(session->audio);
983 linphone_upnp_stream_destroy(session->video);
987 LinphoneUpnpState linphone_upnp_session_get_state(UpnpSession *session) {
988 return session->state;
996 struct linphone_upnp_config_list_port_bindings_struct {
997 struct _LpConfig *lpc;
1001 static void linphone_upnp_config_list_port_bindings_cb(const char *entry, struct linphone_upnp_config_list_port_bindings_struct *cookie) {
1002 char protocol_str[4]; // TCP or UDP
1003 upnp_igd_ip_protocol protocol;
1006 bool_t valid = TRUE;
1007 UpnpPortBinding *port;
1008 if(sscanf(entry, "%3s-%i-%i", protocol_str, &external_port, &local_port) == 3) {
1009 if(strcasecmp(protocol_str, "TCP") == 0) {
1010 protocol = UPNP_IGD_IP_PROTOCOL_TCP;
1011 } else if(strcasecmp(protocol_str, "UDP") == 0) {
1012 protocol = UPNP_IGD_IP_PROTOCOL_UDP;
1017 port = linphone_upnp_port_binding_new();
1018 port->state = LinphoneUpnpStateOk;
1019 port->protocol = protocol;
1020 port->external_port = external_port;
1021 port->local_port = local_port;
1022 cookie->retList = ms_list_append(cookie->retList, port);
1028 ms_warning("uPnP configuration invalid line: %s", entry);
1032 MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc) {
1033 struct linphone_upnp_config_list_port_bindings_struct cookie = {lpc, NULL};
1034 lp_config_for_each_entry(lpc, UPNP_SECTION_NAME, (void(*)(const char *, void*))linphone_upnp_config_list_port_bindings_cb, &cookie);
1035 return cookie.retList;
1038 void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1040 UpnpPortBinding *list_port;
1042 list = lupnp->removing_configs;
1043 while(list != NULL) {
1044 list_port = (UpnpPortBinding *)list->data;
1045 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1046 lupnp->removing_configs = ms_list_remove(lupnp->removing_configs, list_port);
1047 linphone_upnp_port_binding_release(list_port);
1050 list = ms_list_next(list);
1053 list = lupnp->adding_configs;
1054 while(list != NULL) {
1055 list_port = (UpnpPortBinding *)list->data;
1056 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1059 list = ms_list_next(list);
1062 list_port = linphone_upnp_port_binding_copy(port);
1063 lupnp->adding_configs = ms_list_append(lupnp->adding_configs, list_port);
1066 void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1068 UpnpPortBinding *list_port;
1070 list = lupnp->adding_configs;
1071 while(list != NULL) {
1072 list_port = (UpnpPortBinding *)list->data;
1073 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1074 lupnp->adding_configs = ms_list_remove(lupnp->adding_configs, list_port);
1075 linphone_upnp_port_binding_release(list_port);
1078 list = ms_list_next(list);
1081 list = lupnp->removing_configs;
1082 while(list != NULL) {
1083 list_port = (UpnpPortBinding *)list->data;
1084 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1087 list = ms_list_next(list);
1090 list_port = linphone_upnp_port_binding_copy(port);
1091 lupnp->removing_configs = ms_list_append(lupnp->removing_configs, list_port);