#include "private.h"
#define UPNP_MAX_RETRY 4
+#define UPNP_SECTION_NAME "uPnP"
+
+/* Define private types */
+typedef struct _LpItem{
+ char *key;
+ char *value;
+} LpItem;
+
+typedef struct _LpSection{
+ char *name;
+ MSList *items;
+} LpSection;
+
+typedef struct _LpConfig{
+ FILE *file;
+ char *filename;
+ MSList *sections;
+ int modified;
+ int readonly;
+} LpConfig;
+
+/* Declare private functions */
+LpSection *lp_config_find_section(LpConfig *lpconfig, const char *name);
+void lp_section_remove_item(LpSection *sec, LpItem *item);
+void lp_config_set_string(LpConfig *lpconfig,const char *section, const char *key, const char *value);
+
+bool_t linphone_core_upnp_hook(void *data);
UpnpPortBinding *upnp_port_binding_new();
-UpnpPortBinding * upnp_port_binding_retain(UpnpPortBinding *port);
+UpnpPortBinding *upnp_port_binding_copy(const UpnpPortBinding *port);
+bool_t upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2);
+UpnpPortBinding *upnp_port_binding_retain(UpnpPortBinding *port);
void upnp_port_binding_release(UpnpPortBinding *port);
+MSList *upnp_config_list_port_bindings(struct _LpConfig *lpc);
+int upnp_config_add_port_binding(LinphoneCore *lc, const UpnpPortBinding *port);
+int upnp_config_remove_port_binding(LinphoneCore *lc, const UpnpPortBinding *port);
+
int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *port);
int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port);
+/**
+ * uPnP Callbacks
+ */
+
/* Convert uPnP IGD logs to ortp logs */
void linphone_upnp_igd_print(void *cookie, upnp_igd_print_level level, const char *fmt, va_list list) {
int ortp_level = ORTP_DEBUG;
const char *ip_address = NULL;
const char *connection_status = NULL;
bool_t nat_enabled = FALSE;
-
ms_mutex_lock(&lupnp->mutex);
switch(event) {
nat_enabled = upnp_igd_get_nat_enabled(lupnp->upnp_igd_ctxt);
if(ip_address == NULL || connection_status == NULL) {
- lupnp->state = UPNP_Pending;
+ ms_message("uPnP IGD: Pending");
+ lupnp->state = LinphoneUpnpStatePending;
} else if(strcasecmp(connection_status, "Connected") || !nat_enabled) {
- lupnp->state = UPNP_Ko;
+ ms_message("uPnP IGD: Not Available");
+ lupnp->state = LinphoneUpnpStateNotAvailable;
} else {
- // Emit add port binding
- // Emit remove old port binding
- lupnp->state = UPNP_Ok;
+ ms_message("uPnP IGD: Connected");
+ lupnp->state = LinphoneUpnpStateOk;
}
break;
case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS:
mapping = (upnp_igd_port_mapping *) arg;
port_mapping = (UpnpPortBinding*) mapping->cookie;
- port_mapping->remote_port = mapping->remote_port;
- port_mapping->state = UPNP_Ok;
- // TODO: SAVE IN CONFIG THE PORT
+ port_mapping->external_port = mapping->remote_port;
+ port_mapping->state = LinphoneUpnpStateOk;
+ ms_message("uPnP IGD: Added port binding %s|%d->%s:%d",
+ (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
+ port_mapping->external_port,
+ port_mapping->local_addr,
+ port_mapping->local_port);
+ upnp_config_add_port_binding(lc, port_mapping);
+
upnp_port_binding_release(port_mapping);
break;
case UPNP_IGD_PORT_MAPPING_ADD_FAILURE:
mapping = (upnp_igd_port_mapping *) arg;
port_mapping = (UpnpPortBinding*) mapping->cookie;
- upnp_context_send_add_port_binding(lc, port_mapping);
+ if(upnp_context_send_add_port_binding(lc, port_mapping) != 0) {
+ ms_error("uPnP IGD: Can't add port binding %s|%d->%s:%d",
+ (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
+ port_mapping->external_port,
+ port_mapping->local_addr,
+ port_mapping->local_port);
+ }
+
upnp_port_binding_release(port_mapping);
break;
case UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS:
mapping = (upnp_igd_port_mapping *) arg;
port_mapping = (UpnpPortBinding*) mapping->cookie;
- port_mapping->remote_port = -1;
- port_mapping->state = UPNP_Idle;
- // TODO: REMOVE FROM CONFIG THE PORT
+ port_mapping->state = LinphoneUpnpStateIdle;
+ ms_message("uPnP IGD: Removed port binding %s|%d->%d",
+ (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
+ port_mapping->external_port,
+ port_mapping->local_port);
+ upnp_config_remove_port_binding(lc, port_mapping);
+
upnp_port_binding_release(port_mapping);
break;
case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE:
mapping = (upnp_igd_port_mapping *) arg;
port_mapping = (UpnpPortBinding*) mapping->cookie;
- upnp_context_send_remove_port_binding(lc, port_mapping);
- // TODO: REMOVE FROM CONFIG THE PORT (DON'T TRY ANYMORE)
+ if(upnp_context_send_remove_port_binding(lc, port_mapping) != 0) {
+ ms_error("uPnP IGD: Can't remove port binding %s|%d->%d",
+ (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
+ port_mapping->external_port,
+ port_mapping->local_port);
+ upnp_config_remove_port_binding(lc, port_mapping);
+ }
+
upnp_port_binding_release(port_mapping);
break;
ms_mutex_unlock(&lupnp->mutex);
}
+
+/**
+ * uPnP Context
+ */
+
+int upnp_context_init(LinphoneCore *lc) {
+ LCSipTransports transport;
+ UpnpContext *lupnp = &lc->upnp;
+ const char *ip_address;
+
+ ms_mutex_init(&lupnp->mutex, NULL);
+ lupnp->pending_configs = NULL;
+ lupnp->state = LinphoneUpnpStateIdle;
+ lupnp->old_state = LinphoneUpnpStateIdle;
+ ms_message("uPnP IGD: Init");
+
+ linphone_core_get_sip_transports(lc, &transport);
+ if(transport.udp_port != 0) {
+ lupnp->sip_udp = upnp_port_binding_new();
+ lupnp->sip_udp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
+ lupnp->sip_udp->local_port = transport.udp_port;
+ } else {
+ lupnp->sip_udp = NULL;
+ }
+ if(transport.tcp_port != 0) {
+ lupnp->sip_tcp = upnp_port_binding_new();
+ lupnp->sip_tcp->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
+ lupnp->sip_tcp->local_port = transport.tcp_port;
+ } else {
+ lupnp->sip_tcp = NULL;
+ }
+ if(transport.tls_port != 0) {
+ lupnp->sip_tls = upnp_port_binding_new();
+ lupnp->sip_tls->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
+ lupnp->sip_tls->local_port = transport.tls_port;
+ } else {
+ lupnp->sip_tls = NULL;
+ }
+
+ linphone_core_add_iterate_hook(lc, linphone_core_upnp_hook, lc);
+
+ lupnp->upnp_igd_ctxt = NULL;
+ lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, lc);
+ if(lupnp->upnp_igd_ctxt == NULL) {
+ lupnp->state = LinphoneUpnpStateKo;
+ ms_error("Can't create uPnP IGD context");
+ return -1;
+ }
+
+ ip_address = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
+ if(lupnp->sip_udp != NULL) {
+ strncpy(lupnp->sip_udp->local_addr, ip_address, sizeof(lupnp->sip_udp->local_addr));
+ }
+ if(lupnp->sip_tcp != NULL) {
+ strncpy(lupnp->sip_tcp->local_addr, ip_address, sizeof(lupnp->sip_tcp->local_addr));
+ }
+ if(lupnp->sip_tls != NULL) {
+ strncpy(lupnp->sip_tls->local_addr, ip_address, sizeof(lupnp->sip_tls->local_addr));
+ }
+
+ lupnp->state = LinphoneUpnpStatePending;
+ return 0;
+}
+
+void upnp_context_uninit(LinphoneCore *lc) {
+ UpnpContext *lupnp = &lc->upnp;
+ linphone_core_remove_iterate_hook(lc, linphone_core_upnp_hook, lc);
+
+ if(lupnp->sip_udp != NULL) {
+ upnp_port_binding_release(lupnp->sip_udp);
+ }
+ if(lupnp->sip_tcp != NULL) {
+ upnp_port_binding_release(lupnp->sip_tcp);
+ }
+ if(lupnp->sip_tls != NULL) {
+ upnp_port_binding_release(lupnp->sip_tls);
+ }
+ if(lupnp->upnp_igd_ctxt != NULL) {
+ upnp_igd_destroy(lupnp->upnp_igd_ctxt);
+ }
+ ms_mutex_destroy(&lupnp->mutex);
+
+ ms_message("uPnP IGD: Uninit");
+}
+
int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port) {
UpnpContext *lupnp = &lc->upnp;
upnp_igd_port_mapping mapping;
- char * local_host = NULL;
int ret;
- if(port->state == UPNP_Idle) {
- port->remote_port = -1;
+ if(port->state == LinphoneUpnpStateIdle) {
+ port->external_port = -1;
port->retry = 0;
- port->state = UPNP_Pending;
+ port->state = LinphoneUpnpStateAdding;
+ } else if(port->state != LinphoneUpnpStateAdding) {
+ ms_error("uPnP: try to add a port binding in wrong state: %d", port->state);
+ return -2;
}
+
if(port->retry >= UPNP_MAX_RETRY) {
ret = -1;
} else {
- local_host = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
mapping.cookie = upnp_port_binding_retain(port);
mapping.local_port = port->local_port;
- mapping.local_host = local_host;
- mapping.remote_port = rand()%1024 + 1024;
+ mapping.local_host = port->local_addr;
+ if(port->external_port == -1)
+ mapping.remote_port = rand()%1024 + 1024; // TODO: use better method
+ else
+ mapping.remote_port = port->external_port;
mapping.remote_host = "";
mapping.description = PACKAGE_NAME;
mapping.protocol = port->protocol;
port->retry++;
ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
- if(local_host != NULL) {
- free(local_host);
- }
}
if(ret != 0) {
- port->state = UPNP_Ko;
+ port->state = LinphoneUpnpStateKo;
}
return ret;
}
UpnpContext *lupnp = &lc->upnp;
upnp_igd_port_mapping mapping;
int ret;
- if(port->state == UPNP_Idle) {
+ if(port->state == LinphoneUpnpStateIdle) {
port->retry = 0;
- port->state = UPNP_Pending;
+ port->state = LinphoneUpnpStateRemoving;
+ } else if(port->state != LinphoneUpnpStateRemoving) {
+ ms_error("uPnP: try to remove a port binding in wrong state: %d", port->state);
+ return -2;
}
+
if(port->retry >= UPNP_MAX_RETRY) {
ret = -1;
} else {
mapping.cookie = upnp_port_binding_retain(port);
- mapping.remote_port = port->remote_port;
+ mapping.remote_port = port->external_port;
mapping.remote_host = "";
mapping.protocol = port->protocol;
port->retry++;
ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
}
if(ret != 0) {
- port->state = UPNP_Ko;
+ port->state = LinphoneUpnpStateKo;
}
return ret;
}
+/*
+ * uPnP Core interfaces
+ */
+
int upnp_call_process(LinphoneCall *call) {
LinphoneCore *lc = call->core;
UpnpContext *lupnp = &lc->upnp;
int ret = -1;
+ UpnpState oldState;
+ const char *local_addr, *external_addr;
ms_mutex_lock(&lupnp->mutex);
// Don't handle when the call
- if(lupnp->state != UPNP_Ko && call->upnp_session != NULL) {
+ if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
ret = 0;
+ local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
+ external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
/*
* Audio part
*/
- call->upnp_session->audio_rtp->local_port = call->audio_port;
- call->upnp_session->audio_rtcp->local_port = call->audio_port+1;
- if(call->upnp_session->audio_rtp->state == UPNP_Idle && call->audiostream != NULL) {
+ strncpy(call->upnp_session->audio->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
+ strncpy(call->upnp_session->audio->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
+ call->upnp_session->audio->rtp->local_port = call->audio_port;
+ strncpy(call->upnp_session->audio->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
+ strncpy(call->upnp_session->audio->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
+ call->upnp_session->audio->rtcp->local_port = call->audio_port+1;
+ if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle && call->audiostream != NULL) {
// Add audio port binding
- upnp_context_send_add_port_binding(lc, call->upnp_session->audio_rtp);
- } else if(call->upnp_session->audio_rtp->state == UPNP_Ok && call->audiostream == NULL) {
+ upnp_context_send_add_port_binding(lc, call->upnp_session->audio->rtp);
+ } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk && call->audiostream == NULL) {
// Remove audio port binding
- upnp_context_send_remove_port_binding(lc, call->upnp_session->audio_rtp);
+ upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtp);
}
- if(call->upnp_session->audio_rtcp->state == UPNP_Idle && call->audiostream != NULL) {
+ if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle && call->audiostream != NULL) {
// Add audio port binding
- upnp_context_send_add_port_binding(lc, call->upnp_session->audio_rtcp);
- } else if(call->upnp_session->audio_rtcp->state == UPNP_Ok && call->audiostream == NULL) {
+ upnp_context_send_add_port_binding(lc, call->upnp_session->audio->rtcp);
+ } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk && call->audiostream == NULL) {
// Remove audio port binding
- upnp_context_send_remove_port_binding(lc, call->upnp_session->audio_rtcp);
+ upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtcp);
+ }
+ if((call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle) &&
+ (call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle)) {
+ call->upnp_session->audio->state = LinphoneUpnpStateOk;
+ } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateAdding ||
+ call->upnp_session->audio->rtp->state == LinphoneUpnpStateRemoving ||
+ call->upnp_session->audio->rtcp->state == LinphoneUpnpStateAdding ||
+ call->upnp_session->audio->rtcp->state == LinphoneUpnpStateRemoving) {
+ call->upnp_session->audio->state = LinphoneUpnpStatePending;
+ } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateKo ||
+ call->upnp_session->audio->rtp->state == LinphoneUpnpStateKo) {
+ call->upnp_session->audio->state = LinphoneUpnpStateKo;
+ } else {
+ call->upnp_session->audio->state = LinphoneUpnpStateIdle;
}
/*
* Video part
*/
- call->upnp_session->video_rtp->local_port = call->video_port;
- call->upnp_session->video_rtcp->local_port = call->video_port+1;
- if(call->upnp_session->video_rtp->state == UPNP_Idle && call->videostream != NULL) {
+ strncpy(call->upnp_session->video->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
+ strncpy(call->upnp_session->video->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
+ call->upnp_session->video->rtp->local_port = call->video_port;
+ strncpy(call->upnp_session->video->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
+ strncpy(call->upnp_session->video->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
+ call->upnp_session->video->rtcp->local_port = call->video_port+1;
+ if(call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle && call->videostream != NULL) {
// Add video port binding
- upnp_context_send_add_port_binding(lc, call->upnp_session->video_rtp);
- } else if(call->upnp_session->video_rtp->state == UPNP_Ok && call->videostream == NULL) {
+ upnp_context_send_add_port_binding(lc, call->upnp_session->video->rtp);
+ } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateOk && call->videostream == NULL) {
// Remove video port binding
- upnp_context_send_remove_port_binding(lc, call->upnp_session->video_rtp);
+ upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtp);
}
- if(call->upnp_session->video_rtcp->state == UPNP_Idle && call->videostream != NULL) {
+ if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle && call->videostream != NULL) {
// Add video port binding
- upnp_context_send_add_port_binding(lc, call->upnp_session->video_rtcp);
- } else if(call->upnp_session->video_rtcp->state == UPNP_Ok && call->videostream == NULL) {
+ upnp_context_send_add_port_binding(lc, call->upnp_session->video->rtcp);
+ } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk && call->videostream == NULL) {
// Remove video port binding
- upnp_context_send_remove_port_binding(lc, call->upnp_session->video_rtcp);
+ upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtcp);
+ }
+ if((call->upnp_session->video->rtp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle) &&
+ (call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle)) {
+ call->upnp_session->video->state = LinphoneUpnpStateOk;
+ } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateAdding ||
+ call->upnp_session->video->rtp->state == LinphoneUpnpStateRemoving ||
+ call->upnp_session->video->rtcp->state == LinphoneUpnpStateAdding ||
+ call->upnp_session->video->rtcp->state == LinphoneUpnpStateRemoving) {
+ call->upnp_session->video->state = LinphoneUpnpStatePending;
+ } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateKo ||
+ call->upnp_session->video->rtp->state == LinphoneUpnpStateKo) {
+ call->upnp_session->video->state = LinphoneUpnpStateKo;
+ } else {
+ call->upnp_session->video->state = LinphoneUpnpStateIdle;
+ }
+
+ /*
+ * Update session state
+ */
+ oldState = call->upnp_session->state;
+ if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
+ call->upnp_session->video->state == LinphoneUpnpStateOk) {
+ call->upnp_session->state = LinphoneUpnpStateOk;
+ } else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
+ call->upnp_session->video->state == LinphoneUpnpStatePending) {
+ call->upnp_session->state = LinphoneUpnpStatePending;
+ } else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
+ call->upnp_session->video->state == LinphoneUpnpStateKo) {
+ call->upnp_session->state = LinphoneUpnpStateKo;
+ } else {
+ call->upnp_session->state = LinphoneUpnpStateIdle;
+ }
+
+ /* When change is done proceed update */
+ if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
+ (call->upnp_session->state == LinphoneUpnpStateOk || call->upnp_session->state == LinphoneUpnpStateKo)) {
+ switch (call->state) {
+ case LinphoneCallUpdating:
+ linphone_core_start_update_call(call->core, call);
+ break;
+ case LinphoneCallUpdatedByRemote:
+ linphone_core_start_accept_call_update(call->core, call);
+ break;
+ case LinphoneCallOutgoingInit:
+ linphone_core_proceed_with_invite_if_ready(call->core, call, NULL);
+ break;
+ case LinphoneCallIdle:
+ linphone_core_notify_incoming_call(call->core, call);
+ break;
+ default:
+ break;
+ }
}
}
+
ms_mutex_unlock(&lupnp->mutex);
return ret;
}
return upnp_call_process(call);
}
-int upnp_context_init(LinphoneCore *lc) {
- LCSipTransports transport;
+bool_t linphone_core_upnp_hook(void *data) {
+ char key[64];
+ MSList *port_bindings = NULL;
+ MSList *port_bindings_item;
+ UpnpPortBinding *port_mapping;
+ LinphoneCore *lc = (LinphoneCore *)data;
UpnpContext *lupnp = &lc->upnp;
- ms_mutex_init(&lupnp->mutex, NULL);
- lupnp->state = UPNP_Idle;
+ ms_mutex_lock(&lupnp->mutex);
- linphone_core_get_sip_transports(lc, &transport);
- if(transport.udp_port != 0) {
- lupnp->sip_udp = upnp_port_binding_new();
- lupnp->sip_udp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
- } else {
- lupnp->sip_udp = NULL;
- }
- if(transport.tcp_port != 0) {
- lupnp->sip_tcp = upnp_port_binding_new();
- lupnp->sip_tcp->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
- } else {
- lupnp->sip_tcp = NULL;
+ if(lupnp->state == LinphoneUpnpStateOk && lupnp->old_state != LinphoneUpnpStateOk) {
+ // Remove old mapping
+ port_bindings = upnp_config_list_port_bindings(lc->config);
+ if(port_bindings != NULL) {
+ for(port_bindings_item = port_bindings;port_bindings_item!=NULL;port_bindings_item=port_bindings_item->next) {
+ port_mapping = (UpnpPortBinding *)port_bindings_item->data;
+ upnp_context_send_remove_port_binding(lc, port_mapping);
+ }
+ ms_list_for_each(port_bindings,(void (*)(void*))upnp_port_binding_release);
+ port_bindings = ms_list_free(port_bindings);
+ }
}
- if(transport.tls_port != 0) {
- lupnp->sip_tls = upnp_port_binding_new();
- lupnp->sip_tls->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
- } else {
- lupnp->sip_tls = NULL;
+
+ if(lupnp->state == LinphoneUpnpStateOk && lupnp->old_state != LinphoneUpnpStateOk) {
+ // Add port bindings
+ if(lupnp->sip_udp != NULL) {
+ upnp_context_send_add_port_binding(lc, lupnp->sip_udp);
+ }
+ if(lupnp->sip_tcp != NULL) {
+ upnp_context_send_add_port_binding(lc, lupnp->sip_tcp);
+ }
+ if(lupnp->sip_tls != NULL) {
+ upnp_context_send_add_port_binding(lc, lupnp->sip_tls);
+ }
}
- lupnp->upnp_igd_ctxt = NULL;
- lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, lc);
- if(lupnp->upnp_igd_ctxt == NULL ) {
- lupnp->state = UPNP_Ko;
- ms_error("Can't create uPnP IGD context");
- return -1;
+
+ /* Update configs */
+ for(port_bindings_item = lupnp->pending_configs;port_bindings_item!=NULL;port_bindings_item=port_bindings_item->next) {
+ port_mapping = (UpnpPortBinding *)port_bindings_item->data;
+ if(port_mapping->state == LinphoneUpnpStateAdding) {
+ snprintf(key, sizeof(key), "%s-%d-%d",
+ (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
+ port_mapping->external_port,
+ port_mapping->local_port);
+ lp_config_set_string(lc->config, UPNP_SECTION_NAME, key, "");
+ }
+ if(port_mapping->state == LinphoneUpnpStateRemoving) {
+ snprintf(key, sizeof(key), "%s-%d-%d",
+ (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
+ port_mapping->external_port,
+ port_mapping->local_port);
+ lp_config_set_string(lc->config, UPNP_SECTION_NAME, key, NULL);
+ }
}
- lupnp->state = UPNP_Pending;
- return 0;
+ ms_list_for_each(lupnp->pending_configs,(void (*)(void*))upnp_port_binding_release);
+ lupnp->pending_configs = ms_list_free(lupnp->pending_configs);
+
+ lupnp->old_state = lupnp->state;
+ ms_mutex_unlock(&lupnp->mutex);
+ return TRUE;
}
-void upnp_context_uninit(LinphoneCore *lc) {
- // Emit remove port (sip & saved)
- UpnpContext *lupnp = &lc->upnp;
- if(lupnp->sip_udp != NULL) {
- upnp_port_binding_release(lupnp->sip_udp);
- }
- if(lupnp->sip_tcp != NULL) {
- upnp_port_binding_release(lupnp->sip_tcp);
- }
- if(lupnp->sip_tls != NULL) {
- upnp_port_binding_release(lupnp->sip_tls);
- }
- if(lupnp->upnp_igd_ctxt != NULL) {
- upnp_igd_destroy(lupnp->upnp_igd_ctxt);
+void linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
+ int i;
+ SalStreamDescription *stream;
+ UpnpStream *upnpStream;
+
+ for (i = 0; i < desc->nstreams; i++) {
+ stream = &desc->streams[i];
+ upnpStream = NULL;
+ if(stream->type == SalAudio) {
+ upnpStream = session->audio;
+ } else if(stream->type == SalVideo) {
+ upnpStream = session->video;
+ }
+ if(upnpStream != NULL) {
+ if(upnpStream->rtp->state == LinphoneUpnpStateOk) {
+ strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
+ stream->rtp_port = upnpStream->rtp->external_port;
+ }
+ if(upnpStream->rtcp->state == LinphoneUpnpStateOk) {
+ strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
+ stream->rtcp_port = upnpStream->rtcp->external_port;
+ }
+ }
}
- ms_mutex_destroy(&lupnp->mutex);
}
+
+/*
+ * uPnP Port Binding
+ */
+
UpnpPortBinding *upnp_port_binding_new() {
UpnpPortBinding *port = NULL;
port = ms_new0(UpnpPortBinding,1);
ms_mutex_init(&port->mutex, NULL);
- port->state = UPNP_Idle;
+ port->state = LinphoneUpnpStateIdle;
+ port->local_addr[0] = '\0';
port->local_port = -1;
- port->remote_port = -1;
+ port->external_addr[0] = '\0';
+ port->external_port = -1;
port->ref = 1;
return port;
}
+UpnpPortBinding *upnp_port_binding_copy(const UpnpPortBinding *port) {
+ UpnpPortBinding *new_port = NULL;
+ new_port = ms_new0(UpnpPortBinding,1);
+ memcpy(new_port, port, sizeof(UpnpPortBinding));
+ ms_mutex_init(&new_port->mutex, NULL);
+ new_port->ref = 1;
+ return new_port;
+}
+
+bool_t upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
+ return port1->protocol == port2->protocol && port1->local_port == port2->local_port &&
+ port1->external_port && port2->external_port;
+}
+
UpnpPortBinding *upnp_port_binding_retain(UpnpPortBinding *port) {
ms_mutex_lock(&port->mutex);
port->ref++;
ms_mutex_unlock(&port->mutex);
}
+/*
+ * uPnP Stream
+ */
+
+UpnpStream* upnp_stream_new() {
+ UpnpStream *stream = ms_new0(UpnpStream,1);
+ stream->state = LinphoneUpnpStateIdle;
+ stream->rtp = upnp_port_binding_new();
+ stream->rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
+ stream->rtcp = upnp_port_binding_new();
+ stream->rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
+ return NULL;
+}
+
+void upnp_stream_destroy(UpnpStream* stream) {
+ upnp_port_binding_release(stream->rtp);
+ upnp_port_binding_release(stream->rtcp);
+ ms_free(stream);
+}
+
+
+/*
+ * uPnP Session
+ */
+
UpnpSession* upnp_session_new() {
UpnpSession *session = ms_new0(UpnpSession,1);
- session->state = UPNP_Idle;
- session->audio_rtp = upnp_port_binding_new();
- session->audio_rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
- session->audio_rtcp = upnp_port_binding_new();
- session->audio_rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
- session->video_rtp = upnp_port_binding_new();
- session->video_rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
- session->video_rtcp = upnp_port_binding_new();
- session->video_rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
+ session->state = LinphoneUpnpStateIdle;
+ session->audio = upnp_stream_new();
+ session->video = upnp_stream_new();
return NULL;
}
-void upnp_session_destroy(UpnpSession* session) {
- upnp_port_binding_release(session->audio_rtp);
- upnp_port_binding_release(session->audio_rtcp);
- upnp_port_binding_release(session->video_rtp);
- upnp_port_binding_release(session->video_rtp);
- // TODO: send remove
- ms_free(session);
+void upnp_session_destroy(LinphoneCall* call) {
+ LinphoneCore *lc = call->core;
+
+ /* Remove bindings */
+ if(call->upnp_session->audio->rtp->state != LinphoneUpnpStateKo && call->upnp_session->audio->rtp->state != LinphoneUpnpStateIdle) {
+ upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtp);
+ }
+ if(call->upnp_session->audio->rtcp->state != LinphoneUpnpStateKo && call->upnp_session->audio->rtcp->state != LinphoneUpnpStateIdle) {
+ upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtcp);
+ }
+ if(call->upnp_session->video->rtp->state != LinphoneUpnpStateKo && call->upnp_session->video->rtp->state != LinphoneUpnpStateIdle) {
+ upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtp);
+ }
+ if(call->upnp_session->video->rtcp->state != LinphoneUpnpStateKo && call->upnp_session->video->rtcp->state != LinphoneUpnpStateIdle) {
+ upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtcp);
+ }
+
+ upnp_stream_destroy(call->upnp_session->audio);
+ upnp_stream_destroy(call->upnp_session->video);
+ ms_free(call->upnp_session);
+ call->upnp_session = NULL;
+}
+
+
+/*
+ * uPnP Config
+ */
+
+MSList *upnp_config_list_port_bindings(struct _LpConfig *lpc) {
+ char protocol_str[4]; // TCP or UDP
+ upnp_igd_ip_protocol protocol;
+ int external_port;
+ int local_port;
+ MSList *retList = NULL;
+ UpnpPortBinding *port;
+ bool_t valid;
+ MSList *elem;
+ MSList *prev_elem;
+ LpItem *item;
+ LpSection *sec=lp_config_find_section(lpc, UPNP_SECTION_NAME);
+ if(sec == NULL)
+ return retList;
+
+ elem = sec->items;
+ while(elem != NULL) {
+ item=(LpItem*)elem->data;
+ valid = TRUE;
+ if(sscanf(item->key, "%3s-%i-%i", protocol_str, &external_port, &local_port) == 3) {
+ if(strcasecmp(protocol_str, "TCP") == 0) {
+ protocol = UPNP_IGD_IP_PROTOCOL_TCP;
+ } else if(strcasecmp(protocol_str, "UDP") == 0) {
+ protocol = UPNP_IGD_IP_PROTOCOL_UDP;
+ } else {
+ valid = FALSE;
+ }
+ if(valid) {
+ port = upnp_port_binding_new();
+ port->protocol = protocol;
+ port->external_port = external_port;
+ port->local_port = local_port;
+ retList = ms_list_append(retList, port);
+ }
+ } else {
+ valid = FALSE;
+ }
+ prev_elem = elem;
+ elem = ms_list_next(elem);
+ if(!valid) {
+ ms_warning("uPnP configuration invalid line: %s", item->key);
+ lp_section_remove_item(sec, item);
+ }
+ }
+
+ return retList;
+}
+
+int upnp_config_add_port_binding(LinphoneCore *lc, const UpnpPortBinding *port) {
+ UpnpContext *lupnp = &lc->upnp;
+ MSList *list = lupnp->pending_configs;
+ UpnpPortBinding *list_port;
+ bool_t remove;
+ bool_t add = TRUE;
+ while(list != NULL) {
+ remove = FALSE;
+ list_port = (UpnpPortBinding *)list->data;
+ if(upnp_port_binding_equal(list_port, port) == TRUE) {
+ if(list_port->state == LinphoneUpnpStateAdding) {
+ add = FALSE;
+ break;
+ }
+ if(list_port->state == LinphoneUpnpStateRemoving) {
+ remove = TRUE;
+ break;
+ }
+ }
+ list = ms_list_next(list);
+ }
+
+ if(remove) {
+ lupnp->pending_configs = ms_list_remove(list, list_port);
+ } else if(add) {
+ list_port = upnp_port_binding_copy(port);
+ list_port->state = LinphoneUpnpStateAdding;
+ lupnp->pending_configs = ms_list_append(list, list_port);
+ }
+
+ return 0;
+}
+
+int upnp_config_remove_port_binding(LinphoneCore *lc, const UpnpPortBinding *port) {
+ UpnpContext *lupnp = &lc->upnp;
+ MSList *list = lupnp->pending_configs;
+ UpnpPortBinding *list_port;
+ bool_t remove;
+ bool_t add = TRUE;
+ while(list != NULL) {
+ remove = FALSE;
+ list_port = (UpnpPortBinding *)list->data;
+ if(upnp_port_binding_equal(list_port, port)) {
+ if(list_port->state == LinphoneUpnpStateRemoving) {
+ add = FALSE;
+ break;
+ }
+ if(list_port->state == LinphoneUpnpStateAdding) {
+ remove = TRUE;
+ break;
+ }
+ }
+ list = ms_list_next(list);
+ }
+
+ if(remove) {
+ lupnp->pending_configs = ms_list_remove(list, list_port);
+ } else if(add) {
+ list_port = upnp_port_binding_copy(port);
+ list_port->state = LinphoneUpnpStateRemoving;
+ lupnp->pending_configs = ms_list_append(list, list_port);
+ }
+
+ return 0;
}