+ UpnpContext *lupnp = (UpnpContext *)cookie;
+ upnp_igd_port_mapping *mapping = NULL;
+ UpnpPortBinding *port_mapping = NULL;
+ const char *ip_address = NULL;
+ const char *connection_status = NULL;
+ bool_t nat_enabled = FALSE;
+ LinphoneUpnpState old_state;
+
+ if(lupnp == NULL || lupnp->upnp_igd_ctxt == NULL) {
+ ms_error("uPnP IGD: Invalid context in callback");
+ return;
+ }
+
+ ms_mutex_lock(&lupnp->mutex);
+ old_state = lupnp->state;
+
+ switch(event) {
+ case UPNP_IGD_DEVICE_ADDED:
+ case UPNP_IGD_DEVICE_REMOVED:
+ case UPNP_IGD_EXTERNAL_IPADDRESS_CHANGED:
+ case UPNP_IGD_NAT_ENABLED_CHANGED:
+ case UPNP_IGD_CONNECTION_STATUS_CHANGED:
+ ip_address = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
+ connection_status = upnp_igd_get_connection_status(lupnp->upnp_igd_ctxt);
+ nat_enabled = upnp_igd_get_nat_enabled(lupnp->upnp_igd_ctxt);
+
+ if(ip_address == NULL || connection_status == NULL) {
+ ms_message("uPnP IGD: Pending");
+ lupnp->state = LinphoneUpnpStatePending;
+ } else if(strcasecmp(connection_status, "Connected") || !nat_enabled) {
+ ms_message("uPnP IGD: Not Available");
+ lupnp->state = LinphoneUpnpStateNotAvailable;
+ } else {
+ ms_message("uPnP IGD: Connected");
+ lupnp->state = LinphoneUpnpStateOk;
+ if(old_state != LinphoneUpnpStateOk) {
+ linphone_core_upnp_refresh(lupnp);
+ }
+ }
+
+ break;
+
+ case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS:
+ mapping = (upnp_igd_port_mapping *) arg;
+ port_mapping = (UpnpPortBinding*) mapping->cookie;
+ port_mapping->external_port = mapping->remote_port;
+ port_mapping->state = LinphoneUpnpStateOk;
+ linphone_upnp_port_binding_log(ORTP_MESSAGE, "Added port binding", port_mapping);
+ linphone_upnp_config_add_port_binding(lupnp, port_mapping);
+
+ break;
+
+ case UPNP_IGD_PORT_MAPPING_ADD_FAILURE:
+ mapping = (upnp_igd_port_mapping *) arg;
+ port_mapping = (UpnpPortBinding*) mapping->cookie;
+ port_mapping->external_port = -1; //Force random external port
+ if(linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, TRUE) != 0) {
+ linphone_upnp_port_binding_log(ORTP_ERROR, "Can't add port binding", port_mapping);
+ }
+
+ break;
+
+ case UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS:
+ mapping = (upnp_igd_port_mapping *) arg;
+ port_mapping = (UpnpPortBinding*) mapping->cookie;
+ port_mapping->state = LinphoneUpnpStateIdle;
+ linphone_upnp_port_binding_log(ORTP_MESSAGE, "Removed port binding", port_mapping);
+ linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
+
+ break;
+
+ case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE:
+ mapping = (upnp_igd_port_mapping *) arg;
+ port_mapping = (UpnpPortBinding*) mapping->cookie;
+ if(linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE) != 0) {
+ linphone_upnp_port_binding_log(ORTP_ERROR, "Can't remove port binding", port_mapping);
+ linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ if(port_mapping != NULL) {
+ /*
+ * Execute delayed actions
+ */
+ if(port_mapping->to_remove) {
+ if(port_mapping->state == LinphoneUpnpStateOk) {
+ port_mapping->to_remove = FALSE;
+ linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, FALSE);
+ } else if(port_mapping->state == LinphoneUpnpStateKo) {
+ port_mapping->to_remove = FALSE;
+ }
+ }
+ if(port_mapping->to_add) {
+ if(port_mapping->state == LinphoneUpnpStateIdle || port_mapping->state == LinphoneUpnpStateKo) {
+ port_mapping->to_add = FALSE;
+ linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, FALSE);
+ }
+ }
+
+ lupnp->pending_bindings = ms_list_remove(lupnp->pending_bindings, port_mapping);
+ linphone_upnp_port_binding_release(port_mapping);
+ }
+
+ /*
+ * If there is no pending binding emit a signal
+ */
+ if(lupnp->pending_bindings == NULL) {
+ ms_cond_signal(&lupnp->empty_cond);
+ }
+ ms_mutex_unlock(&lupnp->mutex);
+}
+
+
+/**
+ * uPnP Context
+ */
+
+UpnpContext* linphone_upnp_context_new(LinphoneCore *lc) {
+ UpnpContext *lupnp = (UpnpContext *)ms_new0(UpnpContext,1);
+
+ ms_mutex_init(&lupnp->mutex, NULL);
+ ms_cond_init(&lupnp->empty_cond, NULL);
+
+ lupnp->last_ready_check = 0;
+ lupnp->last_ready_state = LinphoneUpnpStateIdle;
+
+ lupnp->lc = lc;
+ lupnp->pending_bindings = NULL;
+ lupnp->adding_configs = NULL;
+ lupnp->removing_configs = NULL;
+ lupnp->state = LinphoneUpnpStateIdle;
+ ms_message("uPnP IGD: New %p for core %p", lupnp, lc);
+
+ // Init ports
+ lupnp->sip_udp = NULL;
+ lupnp->sip_tcp = NULL;
+ lupnp->sip_tls = NULL;
+
+ linphone_core_add_iterate_hook(lc, linphone_core_upnp_hook, lupnp);
+
+ lupnp->upnp_igd_ctxt = NULL;
+ lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, NULL, lupnp);
+ if(lupnp->upnp_igd_ctxt == NULL) {
+ lupnp->state = LinphoneUpnpStateKo;
+ ms_error("Can't create uPnP IGD context");
+ return NULL;
+ }
+
+ lupnp->state = LinphoneUpnpStatePending;
+ upnp_igd_start(lupnp->upnp_igd_ctxt);
+
+ return lupnp;
+}
+
+void linphone_upnp_context_destroy(UpnpContext *lupnp) {
+ linphone_core_remove_iterate_hook(lupnp->lc, linphone_core_upnp_hook, lupnp);
+
+ ms_mutex_lock(&lupnp->mutex);
+
+ if(lupnp->lc->network_reachable) {
+ /* Send port binding removes */
+ if(lupnp->sip_udp != NULL) {
+ linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_udp, TRUE);
+ }
+ if(lupnp->sip_tcp != NULL) {
+ linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tcp, TRUE);
+ }
+ if(lupnp->sip_tls != NULL) {
+ linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tls, TRUE);
+ }
+ }
+
+ /* Wait all pending bindings are done */
+ if(lupnp->pending_bindings != NULL) {
+ ms_message("uPnP IGD: Wait all pending port bindings ...");
+ ms_cond_wait(&lupnp->empty_cond, &lupnp->mutex);
+ }
+ ms_mutex_unlock(&lupnp->mutex);
+
+ if(lupnp->upnp_igd_ctxt != NULL) {
+ upnp_igd_destroy(lupnp->upnp_igd_ctxt);
+ lupnp->upnp_igd_ctxt = NULL;
+ }
+
+ /* No more multi threading here */
+
+ /* Run one more time configuration update and proxy */
+ linphone_upnp_update_config(lupnp);
+ linphone_upnp_update_proxy(lupnp, TRUE);
+
+ /* Release port bindings */
+ if(lupnp->sip_udp != NULL) {
+ linphone_upnp_port_binding_release(lupnp->sip_udp);
+ lupnp->sip_udp = NULL;
+ }
+ if(lupnp->sip_tcp != NULL) {
+ linphone_upnp_port_binding_release(lupnp->sip_tcp);
+ lupnp->sip_tcp = NULL;
+ }
+ if(lupnp->sip_tls != NULL) {
+ linphone_upnp_port_binding_release(lupnp->sip_tls);
+ lupnp->sip_tcp = NULL;
+ }
+
+ /* Release lists */
+ ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
+ lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
+ ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
+ lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
+ ms_list_for_each(lupnp->pending_bindings,(void (*)(void*))linphone_upnp_port_binding_release);
+ lupnp->pending_bindings = ms_list_free(lupnp->pending_bindings);
+
+ ms_mutex_destroy(&lupnp->mutex);
+ ms_cond_destroy(&lupnp->empty_cond);
+
+ ms_message("uPnP IGD: destroy %p", lupnp);
+ ms_free(lupnp);
+}
+
+LinphoneUpnpState linphone_upnp_context_get_state(UpnpContext *lupnp) {
+ LinphoneUpnpState state = LinphoneUpnpStateKo;
+ if(lupnp != NULL) {
+ ms_mutex_lock(&lupnp->mutex);
+ state = lupnp->state;
+ ms_mutex_unlock(&lupnp->mutex);
+ }
+ return state;
+}
+
+bool_t _linphone_upnp_context_is_ready_for_register(UpnpContext *lupnp) {
+ bool_t ready = TRUE;
+
+ // 1 Check global uPnP state
+ ready = (lupnp->state == LinphoneUpnpStateOk);
+
+ // 2 Check external ip address
+ if(ready) {
+ if (upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt) == NULL) {
+ ready = FALSE;
+ }
+ }
+
+ // 3 Check sip ports bindings
+ if(ready) {
+ if(lupnp->sip_udp != NULL) {
+ if(lupnp->sip_udp->state != LinphoneUpnpStateOk) {
+ ready = FALSE;
+ }
+ } else if(lupnp->sip_tcp != NULL) {
+ if(lupnp->sip_tcp->state != LinphoneUpnpStateOk) {
+ ready = FALSE;
+ }
+ } else if(lupnp->sip_tls != NULL) {
+ if(lupnp->sip_tls->state != LinphoneUpnpStateOk) {
+ ready = FALSE;
+ }
+ } else {
+ ready = FALSE;
+ }
+ }
+
+ return ready;
+}
+
+bool_t linphone_upnp_context_is_ready_for_register(UpnpContext *lupnp) {
+ bool_t ready = FALSE;
+ if(lupnp != NULL) {
+ ms_mutex_lock(&lupnp->mutex);
+ ready = _linphone_upnp_context_is_ready_for_register(lupnp);
+ ms_mutex_unlock(&lupnp->mutex);
+ }
+ return ready;
+}
+
+int linphone_upnp_context_get_external_port(UpnpContext *lupnp) {
+ int port = -1;
+ if(lupnp != NULL) {
+ ms_mutex_lock(&lupnp->mutex);
+
+ if(lupnp->sip_udp != NULL) {
+ if(lupnp->sip_udp->state == LinphoneUpnpStateOk) {
+ port = lupnp->sip_udp->external_port;
+ }
+ } else if(lupnp->sip_tcp != NULL) {
+ if(lupnp->sip_tcp->state == LinphoneUpnpStateOk) {
+ port = lupnp->sip_tcp->external_port;
+ }
+ } else if(lupnp->sip_tls != NULL) {
+ if(lupnp->sip_tls->state == LinphoneUpnpStateOk) {
+ port = lupnp->sip_tls->external_port;
+ }
+ }
+
+ ms_mutex_unlock(&lupnp->mutex);
+ }
+ return port;
+}
+
+void linphone_upnp_refresh(UpnpContext * lupnp) {
+ upnp_igd_refresh(lupnp->upnp_igd_ctxt);
+}
+
+const char* linphone_upnp_context_get_external_ipaddress(UpnpContext *lupnp) {
+ const char* addr = NULL;
+ if(lupnp != NULL) {
+ ms_mutex_lock(&lupnp->mutex);
+ addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
+ ms_mutex_unlock(&lupnp->mutex);
+ }
+ return addr;
+}
+
+int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry) {
+ upnp_igd_port_mapping mapping;
+ char description[128];
+ int ret;
+
+ if(lupnp->state != LinphoneUpnpStateOk) {
+ return -2;
+ }
+
+ // Compute port binding state
+ if(port->state != LinphoneUpnpStateAdding) {
+ port->to_remove = FALSE;
+ switch(port->state) {
+ case LinphoneUpnpStateKo:
+ case LinphoneUpnpStateIdle: {
+ port->retry = 0;
+ port->state = LinphoneUpnpStateAdding;
+ }
+ break;
+ case LinphoneUpnpStateRemoving: {
+ port->to_add = TRUE;
+ return 0;
+ }
+ break;
+ default:
+ return 0;
+ }
+ }
+
+ // No retry if specified
+ if(port->retry != 0 && !retry) {
+ return -1;
+ }
+
+ if(port->retry >= UPNP_ADD_MAX_RETRY) {
+ ret = -1;
+ } else {
+ linphone_upnp_port_binding_set_device_id(port, upnp_igd_get_device_id(lupnp->upnp_igd_ctxt));
+ mapping.cookie = linphone_upnp_port_binding_retain(port);
+ lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
+
+ mapping.local_port = port->local_port;
+ mapping.local_host = port->local_addr;
+ if(port->external_port == -1)
+ port->external_port = rand()%(0xffff - 1024) + 1024;
+ mapping.remote_port = port->external_port;
+ mapping.remote_host = "";
+ snprintf(description, 128, "%s %s at %s:%d",
+ PACKAGE_NAME,
+ (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP": "UDP",
+ port->local_addr, port->local_port);
+ mapping.description = description;
+ mapping.protocol = port->protocol;
+
+ port->retry++;
+ linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to add port binding", port);
+ ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
+ }
+ if(ret != 0) {
+ port->state = LinphoneUpnpStateKo;
+ }
+ return ret;
+}
+
+int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry) {
+ upnp_igd_port_mapping mapping;
+ int ret;
+
+ if(lupnp->state != LinphoneUpnpStateOk) {
+ return -2;
+ }
+
+ // Compute port binding state
+ if(port->state != LinphoneUpnpStateRemoving) {
+ port->to_add = FALSE;
+ switch(port->state) {
+ case LinphoneUpnpStateOk: {
+ port->retry = 0;
+ port->state = LinphoneUpnpStateRemoving;
+ }
+ break;
+ case LinphoneUpnpStateAdding: {
+ port->to_remove = TRUE;
+ return 0;
+ }
+ break;
+ default:
+ return 0;
+ }
+ }
+
+ // No retry if specified
+ if(port->retry != 0 && !retry) {
+ return 1;
+ }
+
+ if(port->retry >= UPNP_REMOVE_MAX_RETRY) {
+ ret = -1;
+ } else {
+ linphone_upnp_port_binding_set_device_id(port, upnp_igd_get_device_id(lupnp->upnp_igd_ctxt));
+ mapping.cookie = linphone_upnp_port_binding_retain(port);
+ lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
+
+ mapping.remote_port = port->external_port;
+ mapping.remote_host = "";
+ mapping.protocol = port->protocol;
+ port->retry++;
+ linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to remove port binding", port);
+ ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
+ }
+ if(ret != 0) {
+ port->state = LinphoneUpnpStateKo;
+ }
+ return ret;
+}
+
+/*
+ * uPnP Core interfaces
+ */
+
+int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool_t video) {
+ LinphoneCore *lc = call->core;
+ UpnpContext *lupnp = lc->upnp;
+ int ret = -1;
+
+ if(lupnp == NULL) {
+ return ret;
+ }
+
+ ms_mutex_lock(&lupnp->mutex);
+
+ // Don't handle when the call
+ if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
+ ret = 0;
+
+ /*
+ * Audio part
+ */
+ linphone_upnp_update_port_binding(lupnp, &call->upnp_session->audio->rtp,
+ UPNP_IGD_IP_PROTOCOL_UDP, (audio)? call->audio_port:0, UPNP_CALL_RETRY_DELAY);
+
+ linphone_upnp_update_port_binding(lupnp, &call->upnp_session->audio->rtcp,
+ UPNP_IGD_IP_PROTOCOL_UDP, (audio)? call->audio_port+1:0, UPNP_CALL_RETRY_DELAY);
+
+ /*
+ * Video part
+ */
+ linphone_upnp_update_port_binding(lupnp, &call->upnp_session->video->rtp,
+ UPNP_IGD_IP_PROTOCOL_UDP, (video)? call->video_port:0, UPNP_CALL_RETRY_DELAY);
+
+ linphone_upnp_update_port_binding(lupnp, &call->upnp_session->video->rtcp,
+ UPNP_IGD_IP_PROTOCOL_UDP, (video)? call->video_port+1:0, UPNP_CALL_RETRY_DELAY);
+ }
+
+ ms_mutex_unlock(&lupnp->mutex);
+
+ /*
+ * Update uPnP call state
+ */
+ linphone_upnp_call_process(call);
+
+ return ret;
+}
+
+
+
+int linphone_core_update_upnp_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) {
+ bool_t audio = FALSE;
+ bool_t video = FALSE;
+ int i;
+ const SalStreamDescription *stream;
+
+ for (i = 0; i < md->n_total_streams; i++) {
+ stream = &md->streams[i];
+ if(stream->type == SalAudio) {
+ audio = TRUE;
+ } else if(stream->type == SalVideo) {
+ video = TRUE;
+ }
+ }
+
+ return linphone_core_update_upnp_audio_video(call, audio, video);
+}
+
+int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
+ return linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
+}
+
+void linphone_core_update_upnp_state_in_call_stats(LinphoneCall *call) {
+ call->stats[LINPHONE_CALL_STATS_AUDIO].upnp_state = call->upnp_session->audio->state;
+ call->stats[LINPHONE_CALL_STATS_VIDEO].upnp_state = call->upnp_session->video->state;