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_MAX_RETRY 4
24 #define UPNP_SECTION_NAME "uPnP"
26 /* Define private types */
27 typedef struct _LpItem{
32 typedef struct _LpSection{
37 typedef struct _LpConfig{
45 /* Declare private functions */
46 LpSection *lp_config_find_section(LpConfig *lpconfig, const char *name);
47 void lp_section_remove_item(LpSection *sec, LpItem *item);
48 void lp_config_set_string(LpConfig *lpconfig,const char *section, const char *key, const char *value);
50 bool_t linphone_core_upnp_hook(void *data);
52 UpnpPortBinding *upnp_port_binding_new();
53 UpnpPortBinding *upnp_port_binding_copy(const UpnpPortBinding *port);
54 bool_t upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2);
55 UpnpPortBinding *upnp_port_binding_retain(UpnpPortBinding *port);
56 void upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port);
57 void upnp_port_binding_release(UpnpPortBinding *port);
59 MSList *upnp_config_list_port_bindings(struct _LpConfig *lpc);
60 int upnp_config_add_port_binding(LinphoneCore *lc, const UpnpPortBinding *port);
61 int upnp_config_remove_port_binding(LinphoneCore *lc, const UpnpPortBinding *port);
63 int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *port);
64 int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port);
71 /* Convert uPnP IGD logs to ortp logs */
72 void linphone_upnp_igd_print(void *cookie, upnp_igd_print_level level, const char *fmt, va_list list) {
73 int ortp_level = ORTP_DEBUG;
75 case UPNP_IGD_MESSAGE:
76 ortp_level = ORTP_MESSAGE;
78 case UPNP_IGD_WARNING:
79 ortp_level = ORTP_DEBUG; // Too verbose otherwise
82 ortp_level = ORTP_DEBUG; // Too verbose otherwise
87 ortp_logv(ortp_level, fmt, list);
90 void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) {
91 LinphoneCore *lc = (LinphoneCore *)cookie;
92 UpnpContext *lupnp = &lc->upnp;
93 upnp_igd_port_mapping *mapping = NULL;
94 UpnpPortBinding *port_mapping = NULL;
95 const char *ip_address = NULL;
96 const char *connection_status = NULL;
97 bool_t nat_enabled = FALSE;
98 ms_mutex_lock(&lupnp->mutex);
101 case UPNP_IGD_EXTERNAL_IPADDRESS_CHANGED:
102 case UPNP_IGD_NAT_ENABLED_CHANGED:
103 case UPNP_IGD_CONNECTION_STATUS_CHANGED:
104 ip_address = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
105 connection_status = upnp_igd_get_connection_status(lupnp->upnp_igd_ctxt);
106 nat_enabled = upnp_igd_get_nat_enabled(lupnp->upnp_igd_ctxt);
108 if(ip_address == NULL || connection_status == NULL) {
109 ms_message("uPnP IGD: Pending");
110 lupnp->state = LinphoneUpnpStatePending;
111 } else if(strcasecmp(connection_status, "Connected") || !nat_enabled) {
112 ms_message("uPnP IGD: Not Available");
113 lupnp->state = LinphoneUpnpStateNotAvailable;
115 ms_message("uPnP IGD: Connected");
116 lupnp->state = LinphoneUpnpStateOk;
121 case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS:
122 mapping = (upnp_igd_port_mapping *) arg;
123 port_mapping = (UpnpPortBinding*) mapping->cookie;
124 port_mapping->external_port = mapping->remote_port;
125 port_mapping->state = LinphoneUpnpStateOk;
126 upnp_port_binding_log(ORTP_MESSAGE, "Added port binding", port_mapping);
127 upnp_config_add_port_binding(lc, port_mapping);
129 upnp_port_binding_release(port_mapping);
132 case UPNP_IGD_PORT_MAPPING_ADD_FAILURE:
133 mapping = (upnp_igd_port_mapping *) arg;
134 port_mapping = (UpnpPortBinding*) mapping->cookie;
135 port_mapping->external_port = -1; //Force a new random port
136 if(upnp_context_send_add_port_binding(lc, port_mapping) != 0) {
137 upnp_port_binding_log(ORTP_ERROR, "Can't add port binding", port_mapping);
140 upnp_port_binding_release(port_mapping);
143 case UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS:
144 mapping = (upnp_igd_port_mapping *) arg;
145 port_mapping = (UpnpPortBinding*) mapping->cookie;
146 port_mapping->state = LinphoneUpnpStateIdle;
147 upnp_port_binding_log(ORTP_MESSAGE, "Removed port binding", port_mapping);
148 upnp_config_remove_port_binding(lc, port_mapping);
150 upnp_port_binding_release(port_mapping);
153 case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE:
154 mapping = (upnp_igd_port_mapping *) arg;
155 port_mapping = (UpnpPortBinding*) mapping->cookie;
156 if(upnp_context_send_remove_port_binding(lc, port_mapping) != 0) {
157 upnp_port_binding_log(ORTP_ERROR, "Can't remove port binding", port_mapping);
158 upnp_config_remove_port_binding(lc, port_mapping);
161 upnp_port_binding_release(port_mapping);
168 ms_mutex_unlock(&lupnp->mutex);
176 int upnp_context_init(LinphoneCore *lc) {
177 LCSipTransports transport;
178 UpnpContext *lupnp = &lc->upnp;
179 const char *ip_address;
181 ms_mutex_init(&lupnp->mutex, NULL);
182 lupnp->pending_configs = NULL;
183 lupnp->state = LinphoneUpnpStateIdle;
184 lupnp->old_state = LinphoneUpnpStateIdle;
185 ms_message("uPnP IGD: Init");
187 linphone_core_get_sip_transports(lc, &transport);
188 if(transport.udp_port != 0) {
189 lupnp->sip_udp = upnp_port_binding_new();
190 lupnp->sip_udp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
191 lupnp->sip_udp->local_port = transport.udp_port;
192 lupnp->sip_udp->external_port = transport.udp_port;
194 lupnp->sip_udp = NULL;
196 if(transport.tcp_port != 0) {
197 lupnp->sip_tcp = upnp_port_binding_new();
198 lupnp->sip_tcp->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
199 lupnp->sip_tcp->local_port = transport.tcp_port;
200 lupnp->sip_tcp->external_port = transport.tcp_port;
202 lupnp->sip_tcp = NULL;
204 if(transport.tls_port != 0) {
205 lupnp->sip_tls = upnp_port_binding_new();
206 lupnp->sip_tls->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
207 lupnp->sip_tls->local_port = transport.tls_port;
208 lupnp->sip_tls->external_port = transport.tls_port;
210 lupnp->sip_tls = NULL;
213 linphone_core_add_iterate_hook(lc, linphone_core_upnp_hook, lc);
215 lupnp->upnp_igd_ctxt = NULL;
216 lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, lc);
217 if(lupnp->upnp_igd_ctxt == NULL) {
218 lupnp->state = LinphoneUpnpStateKo;
219 ms_error("Can't create uPnP IGD context");
223 ip_address = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
224 if(lupnp->sip_udp != NULL) {
225 strncpy(lupnp->sip_udp->local_addr, ip_address, sizeof(lupnp->sip_udp->local_addr));
227 if(lupnp->sip_tcp != NULL) {
228 strncpy(lupnp->sip_tcp->local_addr, ip_address, sizeof(lupnp->sip_tcp->local_addr));
230 if(lupnp->sip_tls != NULL) {
231 strncpy(lupnp->sip_tls->local_addr, ip_address, sizeof(lupnp->sip_tls->local_addr));
234 lupnp->state = LinphoneUpnpStatePending;
238 void upnp_context_uninit(LinphoneCore *lc) {
239 UpnpContext *lupnp = &lc->upnp;
241 // Not need, all hooks are removed before
242 //linphone_core_remove_iterate_hook(lc, linphone_core_upnp_hook, lc);
244 if(lupnp->sip_udp != NULL) {
245 upnp_port_binding_release(lupnp->sip_udp);
247 if(lupnp->sip_tcp != NULL) {
248 upnp_port_binding_release(lupnp->sip_tcp);
250 if(lupnp->sip_tls != NULL) {
251 upnp_port_binding_release(lupnp->sip_tls);
253 if(lupnp->upnp_igd_ctxt != NULL) {
254 upnp_igd_destroy(lupnp->upnp_igd_ctxt);
256 ms_mutex_destroy(&lupnp->mutex);
258 ms_message("uPnP IGD: Uninit");
261 int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port) {
262 UpnpContext *lupnp = &lc->upnp;
263 upnp_igd_port_mapping mapping;
265 if(port->state == LinphoneUpnpStateIdle) {
267 port->state = LinphoneUpnpStateAdding;
268 } else if(port->state != LinphoneUpnpStateAdding) {
269 ms_error("uPnP: try to add a port binding in wrong state: %d", port->state);
273 if(port->retry >= UPNP_MAX_RETRY) {
276 mapping.cookie = upnp_port_binding_retain(port);
277 mapping.local_port = port->local_port;
278 mapping.local_host = port->local_addr;
279 if(port->external_port == -1)
280 mapping.remote_port = rand()%(0xffff - 1024) + 1024; // TODO: use better method
282 mapping.remote_port = port->external_port;
283 mapping.remote_host = "";
284 mapping.description = PACKAGE_NAME;
285 mapping.protocol = port->protocol;
288 ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
291 port->state = LinphoneUpnpStateKo;
296 int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *port) {
297 UpnpContext *lupnp = &lc->upnp;
298 upnp_igd_port_mapping mapping;
300 if(port->state == LinphoneUpnpStateOk) {
302 port->state = LinphoneUpnpStateRemoving;
303 } else if(port->state != LinphoneUpnpStateRemoving) {
304 ms_error("uPnP: try to remove a port binding in wrong state: %d", port->state);
308 if(port->retry >= UPNP_MAX_RETRY) {
311 mapping.cookie = upnp_port_binding_retain(port);
312 mapping.remote_port = port->external_port;
313 mapping.remote_host = "";
314 mapping.protocol = port->protocol;
316 ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
319 port->state = LinphoneUpnpStateKo;
325 * uPnP Core interfaces
328 int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool_t video) {
329 LinphoneCore *lc = call->core;
330 UpnpContext *lupnp = &lc->upnp;
332 const char *local_addr, *external_addr;
334 ms_mutex_lock(&lupnp->mutex);
335 // Don't handle when the call
336 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
338 local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
339 external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
344 strncpy(call->upnp_session->audio->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
345 strncpy(call->upnp_session->audio->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
346 call->upnp_session->audio->rtp->local_port = call->audio_port;
347 call->upnp_session->audio->rtp->external_port = call->audio_port;
348 strncpy(call->upnp_session->audio->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
349 strncpy(call->upnp_session->audio->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
350 call->upnp_session->audio->rtcp->local_port = call->audio_port+1;
351 call->upnp_session->audio->rtcp->external_port = call->audio_port+1;
352 if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle && audio) {
353 // Add audio port binding
354 upnp_context_send_add_port_binding(lc, call->upnp_session->audio->rtp);
355 } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk && !audio) {
356 // Remove audio port binding
357 upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtp);
359 if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle && audio) {
360 // Add audio port binding
361 upnp_context_send_add_port_binding(lc, call->upnp_session->audio->rtcp);
362 } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk && !audio) {
363 // Remove audio port binding
364 upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtcp);
370 strncpy(call->upnp_session->video->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
371 strncpy(call->upnp_session->video->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
372 call->upnp_session->video->rtp->local_port = call->video_port;
373 call->upnp_session->video->rtp->external_port = call->video_port;
374 strncpy(call->upnp_session->video->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
375 strncpy(call->upnp_session->video->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
376 call->upnp_session->video->rtcp->local_port = call->video_port+1;
377 call->upnp_session->video->rtcp->external_port = call->video_port+1;
378 if(call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle && video) {
379 // Add video port binding
380 upnp_context_send_add_port_binding(lc, call->upnp_session->video->rtp);
381 } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateOk && !video) {
382 // Remove video port binding
383 upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtp);
385 if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle && video) {
386 // Add video port binding
387 upnp_context_send_add_port_binding(lc, call->upnp_session->video->rtcp);
388 } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk && !video) {
389 // Remove video port binding
390 upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtcp);
394 ms_mutex_unlock(&lupnp->mutex);
399 int linphone_core_update_upnp_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) {
400 bool_t audio = FALSE;
401 bool_t video = FALSE;
403 const SalStreamDescription *stream;
405 for (i = 0; i < md->nstreams; i++) {
406 stream = &md->streams[i];
407 if(stream->type == SalAudio) {
409 } else if(stream->type == SalVideo) {
414 return linphone_core_update_upnp_audio_video(call, audio, video);
417 int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
418 return linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
421 int upnp_call_process(LinphoneCall *call) {
422 LinphoneCore *lc = call->core;
423 UpnpContext *lupnp = &lc->upnp;
427 ms_mutex_lock(&lupnp->mutex);
429 // Don't handle when the call
430 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
436 if((call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle) &&
437 (call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle)) {
438 call->upnp_session->audio->state = LinphoneUpnpStateOk;
439 } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateAdding ||
440 call->upnp_session->audio->rtp->state == LinphoneUpnpStateRemoving ||
441 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateAdding ||
442 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateRemoving) {
443 call->upnp_session->audio->state = LinphoneUpnpStatePending;
444 } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateKo ||
445 call->upnp_session->audio->rtp->state == LinphoneUpnpStateKo) {
446 call->upnp_session->audio->state = LinphoneUpnpStateKo;
448 call->upnp_session->audio->state = LinphoneUpnpStateIdle;
454 if((call->upnp_session->video->rtp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle) &&
455 (call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle)) {
456 call->upnp_session->video->state = LinphoneUpnpStateOk;
457 } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateAdding ||
458 call->upnp_session->video->rtp->state == LinphoneUpnpStateRemoving ||
459 call->upnp_session->video->rtcp->state == LinphoneUpnpStateAdding ||
460 call->upnp_session->video->rtcp->state == LinphoneUpnpStateRemoving) {
461 call->upnp_session->video->state = LinphoneUpnpStatePending;
462 } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateKo ||
463 call->upnp_session->video->rtp->state == LinphoneUpnpStateKo) {
464 call->upnp_session->video->state = LinphoneUpnpStateKo;
466 call->upnp_session->video->state = LinphoneUpnpStateIdle;
470 * Update session state
472 oldState = call->upnp_session->state;
473 if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
474 call->upnp_session->video->state == LinphoneUpnpStateOk) {
475 call->upnp_session->state = LinphoneUpnpStateOk;
476 } else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
477 call->upnp_session->video->state == LinphoneUpnpStatePending) {
478 call->upnp_session->state = LinphoneUpnpStatePending;
479 } else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
480 call->upnp_session->video->state == LinphoneUpnpStateKo) {
481 call->upnp_session->state = LinphoneUpnpStateKo;
483 call->upnp_session->state = LinphoneUpnpStateIdle;
486 /* When change is done proceed update */
487 if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
488 (call->upnp_session->state == LinphoneUpnpStateOk || call->upnp_session->state == LinphoneUpnpStateKo)) {
489 if(call->upnp_session->state == LinphoneUpnpStateOk)
490 ms_message("uPnP IGD: uPnP for Call %p is ok", call);
492 ms_message("uPnP IGD: uPnP for Call %p is ko", call);
494 switch (call->state) {
495 case LinphoneCallUpdating:
496 linphone_core_start_update_call(lc, call);
498 case LinphoneCallUpdatedByRemote:
499 linphone_core_start_accept_call_update(lc, call);
501 case LinphoneCallOutgoingInit:
502 linphone_core_proceed_with_invite_if_ready(lc, call, NULL);
504 case LinphoneCallIdle:
505 linphone_core_notify_incoming_call(lc, call);
513 ms_mutex_unlock(&lupnp->mutex);
517 bool_t linphone_core_upnp_hook(void *data) {
519 MSList *port_bindings = NULL;
520 MSList *port_bindings_item;
521 UpnpPortBinding *port_mapping;
522 LinphoneCore *lc = (LinphoneCore *)data;
523 UpnpContext *lupnp = &lc->upnp;
524 ms_mutex_lock(&lupnp->mutex);
526 if(lupnp->state == LinphoneUpnpStateOk && lupnp->old_state != LinphoneUpnpStateOk) {
527 // Remove old mapping
528 port_bindings = upnp_config_list_port_bindings(lc->config);
529 if(port_bindings != NULL) {
530 for(port_bindings_item = port_bindings;port_bindings_item != NULL; port_bindings_item = port_bindings_item->next) {
531 port_mapping = (UpnpPortBinding *)port_bindings_item->data;
532 //TODO: Don't send id it's udp/tcp/tls port binding
533 upnp_context_send_remove_port_binding(lc, port_mapping);
535 ms_list_for_each(port_bindings,(void (*)(void*))upnp_port_binding_release);
536 port_bindings = ms_list_free(port_bindings);
540 if(lupnp->state == LinphoneUpnpStateOk && lupnp->old_state != LinphoneUpnpStateOk) {
542 if(lupnp->sip_udp != NULL) {
543 upnp_context_send_add_port_binding(lc, lupnp->sip_udp);
545 if(lupnp->sip_tcp != NULL) {
546 upnp_context_send_add_port_binding(lc, lupnp->sip_tcp);
548 if(lupnp->sip_tls != NULL) {
549 upnp_context_send_add_port_binding(lc, lupnp->sip_tls);
554 for(port_bindings_item = lupnp->pending_configs;port_bindings_item!=NULL;port_bindings_item=port_bindings_item->next) {
555 port_mapping = (UpnpPortBinding *)port_bindings_item->data;
556 if(port_mapping->state == LinphoneUpnpStateAdding) {
557 snprintf(key, sizeof(key), "%s-%d-%d",
558 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
559 port_mapping->external_port,
560 port_mapping->local_port);
561 lp_config_set_string(lc->config, UPNP_SECTION_NAME, key, "uPnP");
562 upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping);
564 if(port_mapping->state == LinphoneUpnpStateRemoving) {
565 snprintf(key, sizeof(key), "%s-%d-%d",
566 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
567 port_mapping->external_port,
568 port_mapping->local_port);
569 lp_config_set_string(lc->config, UPNP_SECTION_NAME, key, NULL);
570 upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping);
573 ms_list_for_each(lupnp->pending_configs,(void (*)(void*))upnp_port_binding_release);
574 lupnp->pending_configs = ms_list_free(lupnp->pending_configs);
576 lupnp->old_state = lupnp->state;
577 ms_mutex_unlock(&lupnp->mutex);
581 void linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
583 SalStreamDescription *stream;
584 UpnpStream *upnpStream;
586 for (i = 0; i < desc->nstreams; i++) {
587 stream = &desc->streams[i];
589 if(stream->type == SalAudio) {
590 upnpStream = session->audio;
591 } else if(stream->type == SalVideo) {
592 upnpStream = session->video;
594 if(upnpStream != NULL) {
595 if(upnpStream->rtp->state == LinphoneUpnpStateOk) {
596 strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
597 stream->rtp_port = upnpStream->rtp->external_port;
599 if(upnpStream->rtcp->state == LinphoneUpnpStateOk) {
600 strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
601 stream->rtcp_port = upnpStream->rtcp->external_port;
612 UpnpPortBinding *upnp_port_binding_new() {
613 UpnpPortBinding *port = NULL;
614 port = ms_new0(UpnpPortBinding,1);
615 ms_mutex_init(&port->mutex, NULL);
616 port->state = LinphoneUpnpStateIdle;
617 port->local_addr[0] = '\0';
618 port->local_port = -1;
619 port->external_addr[0] = '\0';
620 port->external_port = -1;
625 UpnpPortBinding *upnp_port_binding_copy(const UpnpPortBinding *port) {
626 UpnpPortBinding *new_port = NULL;
627 new_port = ms_new0(UpnpPortBinding,1);
628 memcpy(new_port, port, sizeof(UpnpPortBinding));
629 ms_mutex_init(&new_port->mutex, NULL);
634 void upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port) {
635 if(strlen(port->local_addr)) {
636 ortp_log(level, "uPnP IGD: %s %s|%d->%s:%d", msg,
637 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
642 ortp_log(level, "uPnP IGD: %s %s|%d->%d", msg,
643 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
649 bool_t upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
650 return port1->protocol == port2->protocol &&
651 port1->local_port == port2->local_port &&
652 port1->external_port == port2->external_port;
655 UpnpPortBinding *upnp_port_binding_retain(UpnpPortBinding *port) {
656 ms_mutex_lock(&port->mutex);
658 ms_mutex_unlock(&port->mutex);
662 void upnp_port_binding_release(UpnpPortBinding *port) {
663 ms_mutex_lock(&port->mutex);
664 if(--port->ref == 0) {
665 ms_mutex_unlock(&port->mutex);
666 ms_mutex_destroy(&port->mutex);
670 ms_mutex_unlock(&port->mutex);
678 UpnpStream* upnp_stream_new() {
679 UpnpStream *stream = ms_new0(UpnpStream,1);
680 stream->state = LinphoneUpnpStateIdle;
681 stream->rtp = upnp_port_binding_new();
682 stream->rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
683 stream->rtcp = upnp_port_binding_new();
684 stream->rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
688 void upnp_stream_destroy(UpnpStream* stream) {
689 upnp_port_binding_release(stream->rtp);
690 upnp_port_binding_release(stream->rtcp);
699 UpnpSession* upnp_session_new() {
700 UpnpSession *session = ms_new0(UpnpSession,1);
701 session->state = LinphoneUpnpStateIdle;
702 session->audio = upnp_stream_new();
703 session->video = upnp_stream_new();
707 void upnp_session_destroy(LinphoneCall* call) {
708 LinphoneCore *lc = call->core;
710 /* Remove bindings */
711 if(call->upnp_session->audio->rtp->state != LinphoneUpnpStateKo && call->upnp_session->audio->rtp->state != LinphoneUpnpStateIdle) {
712 upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtp);
714 if(call->upnp_session->audio->rtcp->state != LinphoneUpnpStateKo && call->upnp_session->audio->rtcp->state != LinphoneUpnpStateIdle) {
715 upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtcp);
717 if(call->upnp_session->video->rtp->state != LinphoneUpnpStateKo && call->upnp_session->video->rtp->state != LinphoneUpnpStateIdle) {
718 upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtp);
720 if(call->upnp_session->video->rtcp->state != LinphoneUpnpStateKo && call->upnp_session->video->rtcp->state != LinphoneUpnpStateIdle) {
721 upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtcp);
724 upnp_stream_destroy(call->upnp_session->audio);
725 upnp_stream_destroy(call->upnp_session->video);
726 ms_free(call->upnp_session);
727 call->upnp_session = NULL;
735 MSList *upnp_config_list_port_bindings(struct _LpConfig *lpc) {
736 char protocol_str[4]; // TCP or UDP
737 upnp_igd_ip_protocol protocol;
740 MSList *retList = NULL;
741 UpnpPortBinding *port;
745 LpSection *sec=lp_config_find_section(lpc, UPNP_SECTION_NAME);
750 while(elem != NULL) {
751 item=(LpItem*)elem->data;
753 if(sscanf(item->key, "%3s-%i-%i", protocol_str, &external_port, &local_port) == 3) {
754 if(strcasecmp(protocol_str, "TCP") == 0) {
755 protocol = UPNP_IGD_IP_PROTOCOL_TCP;
756 } else if(strcasecmp(protocol_str, "UDP") == 0) {
757 protocol = UPNP_IGD_IP_PROTOCOL_UDP;
762 port = upnp_port_binding_new();
763 port->state = LinphoneUpnpStateOk;
764 port->protocol = protocol;
765 port->external_port = external_port;
766 port->local_port = local_port;
767 retList = ms_list_append(retList, port);
772 elem = ms_list_next(elem);
774 ms_warning("uPnP configuration invalid line: %s", item->key);
775 lp_section_remove_item(sec, item);
782 int upnp_config_add_port_binding(LinphoneCore *lc, const UpnpPortBinding *port) {
783 UpnpContext *lupnp = &lc->upnp;
784 MSList *list = lupnp->pending_configs;
785 UpnpPortBinding *list_port;
786 bool_t remove = FALSE;
788 while(list != NULL) {
789 list_port = (UpnpPortBinding *)list->data;
790 if(upnp_port_binding_equal(list_port, port) == TRUE) {
791 if(list_port->state == LinphoneUpnpStateAdding) {
795 if(list_port->state == LinphoneUpnpStateRemoving) {
800 list = ms_list_next(list);
804 lupnp->pending_configs = ms_list_remove(list, list_port);
806 list_port = upnp_port_binding_copy(port);
807 list_port->state = LinphoneUpnpStateAdding;
808 lupnp->pending_configs = ms_list_append(list, list_port);
814 int upnp_config_remove_port_binding(LinphoneCore *lc, const UpnpPortBinding *port) {
815 UpnpContext *lupnp = &lc->upnp;
816 MSList *list = lupnp->pending_configs;
817 UpnpPortBinding *list_port;
818 bool_t remove = FALSE;
820 while(list != NULL) {
821 list_port = (UpnpPortBinding *)list->data;
822 if(upnp_port_binding_equal(list_port, port)) {
823 if(list_port->state == LinphoneUpnpStateRemoving) {
827 if(list_port->state == LinphoneUpnpStateAdding) {
832 list = ms_list_next(list);
836 lupnp->pending_configs = ms_list_remove(list, list_port);
838 list_port = upnp_port_binding_copy(port);
839 list_port->state = LinphoneUpnpStateRemoving;
840 lupnp->pending_configs = ms_list_append(list, list_port);