X-Git-Url: http://sjero.net/git/?a=blobdiff_plain;f=coreapi%2Fupnp.c;h=0922dad59eb7aa7c66d8f7517da589715f2c1422;hb=9b7ac9b79375f845edd0e8366e602863a223d2c0;hp=d0b71b3e41ae3523ae7a55716efd926e05f80b2c;hpb=806203ca0a5ba0cf0df7f31b2f8ee51544bcd0fa;p=linphone diff --git a/coreapi/upnp.c b/coreapi/upnp.c index d0b71b3e..0922dad5 100644 --- a/coreapi/upnp.c +++ b/coreapi/upnp.c @@ -19,15 +19,158 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "upnp.h" #include "private.h" +#include "lpconfig.h" +#include -#define UPNP_MAX_RETRY 4 +#define UPNP_STRINGIFY(x) #x +#define UPNP_TOSTRING(x) UPNP_STRINGIFY(x) -UpnpPortBinding *upnp_port_binding_new(); -UpnpPortBinding * upnp_port_binding_retain(UpnpPortBinding *port); -void upnp_port_binding_release(UpnpPortBinding *port); +#define UPNP_ADD_MAX_RETRY 4 +#define UPNP_REMOVE_MAX_RETRY 4 +#define UPNP_SECTION_NAME "uPnP" +#define UPNP_CORE_READY_CHECK 1 +#define UPNP_CORE_RETRY_DELAY 10 +#define UPNP_CALL_RETRY_DELAY 3 +#define UPNP_UUID_LEN 128 +#define UPNP_UUID_LEN_STR UPNP_TOSTRING(UPNP_UUID_LEN) +/* + * uPnP Definitions + */ + +typedef struct _UpnpPortBinding { + ms_mutex_t mutex; + LinphoneUpnpState state; + upnp_igd_ip_protocol protocol; + char *device_id; + char local_addr[LINPHONE_IPADDR_SIZE]; + int local_port; + char external_addr[LINPHONE_IPADDR_SIZE]; + int external_port; + int retry; + int ref; + bool_t to_remove; + bool_t to_add; + time_t last_update; +} UpnpPortBinding; + +typedef struct _UpnpStream { + UpnpPortBinding *rtp; + UpnpPortBinding *rtcp; + LinphoneUpnpState state; +} UpnpStream; + +struct _UpnpSession { + LinphoneCall *call; + UpnpStream *audio; + UpnpStream *video; + LinphoneUpnpState state; +}; + +struct _UpnpContext { + LinphoneCore *lc; + upnp_igd_context *upnp_igd_ctxt; + UpnpPortBinding *sip_tcp; + UpnpPortBinding *sip_tls; + UpnpPortBinding *sip_udp; + LinphoneUpnpState state; + MSList *removing_configs; + MSList *adding_configs; + MSList *pending_bindings; + + ms_mutex_t mutex; + ms_cond_t empty_cond; + + time_t last_ready_check; + LinphoneUpnpState last_ready_state; +}; -int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *port); -int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port); + +bool_t linphone_core_upnp_hook(void *data); +void linphone_core_upnp_refresh(UpnpContext *ctx); + +UpnpPortBinding *linphone_upnp_port_binding_new(); +UpnpPortBinding *linphone_upnp_port_binding_new_with_parameters(upnp_igd_ip_protocol protocol, int local_port, int external_port); +UpnpPortBinding *linphone_upnp_port_binding_new_or_collect(MSList *list, upnp_igd_ip_protocol protocol, int local_port, int external_port); +UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port); +void linphone_upnp_port_binding_set_device_id(UpnpPortBinding *port, const char * device_id); +bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2); +UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port); +UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port); +void linphone_upnp_update_port_binding(UpnpContext *lupnp, UpnpPortBinding **port_mapping, upnp_igd_ip_protocol protocol, int port, int retry_delay); +void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port); +void linphone_upnp_port_binding_release(UpnpPortBinding *port); +void linphone_upnp_update_config(UpnpContext *lupnp); +void linphone_upnp_update_proxy(UpnpContext *lupnp, bool_t force); + +// Configuration +MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc, const char *device_id); +void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port); +void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port); + +// uPnP +int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry); +int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry); + +static int linphone_upnp_strncmpi(const char *str1, const char *str2, int len) { + int i = 0; + char char1, char2; + while(i < len) { + char1 = toupper(*str1); + char2 = toupper(*str2); + if(char1 == '\0' || char1 != char2) { + return char1 - char2; + } + str1++; + str2++; + i++; + } + return 0; +} + +static int linphone_upnp_str_min(const char *str1, const char *str2) { + int len1 = strlen(str1); + int len2 = strlen(str2); + if(len1 > len2) { + return len2; + } + return len1; +} + +char * linphone_upnp_format_device_id(const char *device_id) { + char *ret = NULL; + char *tmp; + char tchar; + bool_t copy; + if(device_id == NULL) { + return ret; + } + ret = ms_new(char, UPNP_UUID_LEN + 1); + tmp = ret; + if(linphone_upnp_strncmpi(device_id, "uuid:", linphone_upnp_str_min(device_id, "uuid:")) == 0) { + device_id += strlen("uuid:"); + } + while(*device_id != '\0' && tmp - ret < UPNP_UUID_LEN) { + copy = FALSE; + tchar = *device_id; + if(tchar >= '0' && tchar <= '9') + copy = TRUE; + if(!copy && tchar >= 'A' && tchar <= 'Z') + copy = TRUE; + if(!copy && tchar >= 'a' && tchar <= 'z') + copy = TRUE; + if(copy) { + *tmp = *device_id; + tmp++; + } + device_id++; + } + *tmp = '\0'; + return ret; +} + +/** + * 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) { @@ -37,10 +180,10 @@ void linphone_upnp_igd_print(void *cookie, upnp_igd_print_level level, const cha ortp_level = ORTP_MESSAGE; break; case UPNP_IGD_WARNING: - ortp_level = ORTP_WARNING; + ortp_level = ORTP_DEBUG; // Too verbose otherwise break; case UPNP_IGD_ERROR: - ortp_level = ORTP_ERROR; + ortp_level = ORTP_DEBUG; // Too verbose otherwise break; default: break; @@ -49,17 +192,25 @@ void linphone_upnp_igd_print(void *cookie, upnp_igd_print_level level, const cha } void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) { - LinphoneCore *lc = (LinphoneCore *)cookie; - UpnpContext *lupnp = &lc->upnp; + 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: @@ -68,13 +219,17 @@ void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) { 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; + if(old_state != LinphoneUpnpStateOk) { + linphone_core_upnp_refresh(lupnp); + } } break; @@ -82,234 +237,912 @@ void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) { 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 - upnp_port_binding_release(port_mapping); + 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; - upnp_context_send_add_port_binding(lc, port_mapping); - upnp_port_binding_release(port_mapping); + 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->remote_port = -1; - port_mapping->state = UPNP_Idle; - // TODO: REMOVE FROM CONFIG THE PORT - upnp_port_binding_release(port_mapping); + 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; - upnp_context_send_remove_port_binding(lc, port_mapping); - // TODO: REMOVE FROM CONFIG THE PORT (DON'T TRY ANYMORE) - upnp_port_binding_release(port_mapping); + 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); } -int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port) { - UpnpContext *lupnp = &lc->upnp; + +/** + * 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 * local_host = NULL; + char description[128]; int ret; - if(port->state == UPNP_Idle) { - port->remote_port = -1; - port->retry = 0; - port->state = UPNP_Pending; + + if(lupnp->state != LinphoneUpnpStateOk) { + return -2; } - if(port->retry >= UPNP_MAX_RETRY) { + + // 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 { - local_host = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt); - mapping.cookie = upnp_port_binding_retain(port); + 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 = local_host; - mapping.remote_port = rand()%1024 + 1024; + 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 = ""; - mapping.description = PACKAGE_NAME; + 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(local_host != NULL) { - free(local_host); - } } if(ret != 0) { - port->state = UPNP_Ko; + port->state = LinphoneUpnpStateKo; } return ret; } -int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *port) { - UpnpContext *lupnp = &lc->upnp; +int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry) { upnp_igd_port_mapping mapping; int ret; - if(port->state == UPNP_Idle) { - port->retry = 0; - port->state = UPNP_Pending; + + 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_MAX_RETRY) { + + if(port->retry >= UPNP_REMOVE_MAX_RETRY) { ret = -1; } else { - mapping.cookie = upnp_port_binding_retain(port); - mapping.remote_port = port->remote_port; + 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 = UPNP_Ko; + port->state = LinphoneUpnpStateKo; } return ret; } -int upnp_call_process(LinphoneCall *call) { +/* + * 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; + 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 != UPNP_Ko && call->upnp_session != NULL) { + if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) { ret = 0; /* * 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) { - // 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) { - // Remove audio port binding - upnp_context_send_remove_port_binding(lc, call->upnp_session->audio_rtp); - } - if(call->upnp_session->audio_rtcp->state == UPNP_Idle && 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) { - // Remove audio port binding - upnp_context_send_remove_port_binding(lc, call->upnp_session->audio_rtcp); - } + 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 */ - 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) { - // 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) { - // Remove video port binding - upnp_context_send_remove_port_binding(lc, call->upnp_session->video_rtp); - } - if(call->upnp_session->video_rtcp->state == UPNP_Idle && 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) { - // Remove video port binding - upnp_context_send_remove_port_binding(lc, call->upnp_session->video_rtcp); - } + 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 upnp_call_process(call); + return linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL); } -int upnp_context_init(LinphoneCore *lc) { - LCSipTransports transport; - UpnpContext *lupnp = &lc->upnp; - ms_mutex_init(&lupnp->mutex, NULL); - lupnp->state = UPNP_Idle; +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; +} - 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; +void linphone_upnp_update_stream_state(UpnpStream *stream) { + if((stream->rtp == NULL || stream->rtp->state == LinphoneUpnpStateOk || stream->rtp->state == LinphoneUpnpStateIdle) && + (stream->rtcp == NULL || stream->rtcp->state == LinphoneUpnpStateOk || stream->rtcp->state == LinphoneUpnpStateIdle)) { + stream->state = LinphoneUpnpStateOk; + } else if((stream->rtp != NULL && + (stream->rtp->state == LinphoneUpnpStateAdding || stream->rtp->state == LinphoneUpnpStateRemoving)) || + (stream->rtcp != NULL && + (stream->rtcp->state == LinphoneUpnpStateAdding || stream->rtcp->state == LinphoneUpnpStateRemoving))) { + stream->state = LinphoneUpnpStatePending; + } else if((stream->rtp != NULL && stream->rtp->state == LinphoneUpnpStateKo) || + (stream->rtcp != NULL && stream->rtcp->state == LinphoneUpnpStateKo)) { + stream->state = LinphoneUpnpStateKo; } else { - lupnp->sip_udp = NULL; + ms_error("Invalid stream %p state", stream); } - 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; +} + +int linphone_upnp_call_process(LinphoneCall *call) { + LinphoneCore *lc = call->core; + UpnpContext *lupnp = lc->upnp; + int ret = -1; + LinphoneUpnpState oldState = 0, newState = 0; + + if(lupnp == NULL) { + return ret; } - 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; + + ms_mutex_lock(&lupnp->mutex); + + // Don't handle when the call + if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) { + ret = 0; + + /* + * Update Audio state + */ + linphone_upnp_update_stream_state(call->upnp_session->audio); + + /* + * Update Video state + */ + linphone_upnp_update_stream_state(call->upnp_session->video); + + /* + * Update stat + */ + linphone_core_update_upnp_state_in_call_stats(call); + + /* + * 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; + } + newState = call->upnp_session->state; } - 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; + + ms_mutex_unlock(&lupnp->mutex); + + /* When change is done proceed update */ + if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo && + (newState == LinphoneUpnpStateOk || newState == LinphoneUpnpStateKo)) { + if(call->upnp_session->state == LinphoneUpnpStateOk) + ms_message("uPnP IGD: uPnP for Call %p is ok", call); + else + ms_message("uPnP IGD: uPnP for Call %p is ko", call); + + switch (call->state) { + case LinphoneCallUpdating: + linphone_core_start_update_call(lc, call); + break; + case LinphoneCallUpdatedByRemote: + linphone_core_start_accept_call_update(lc, call); + break; + case LinphoneCallOutgoingInit: + linphone_core_proceed_with_invite_if_ready(lc, call, NULL); + break; + case LinphoneCallIdle: + linphone_core_notify_incoming_call(lc, call); + break; + default: + break; + } } - lupnp->state = UPNP_Pending; - return 0; + + return ret; } -void upnp_context_uninit(LinphoneCore *lc) { - // Emit remove port (sip & saved) - UpnpContext *lupnp = &lc->upnp; +void linphone_core_upnp_refresh(UpnpContext *lupnp) { + MSList *global_list = NULL; + MSList *list = NULL; + MSList *item; + LinphoneCall *call; + UpnpPortBinding *port_mapping, *port_mapping2; + + ms_message("uPnP IGD: Refresh mappings"); + if(lupnp->sip_udp != NULL) { - upnp_port_binding_release(lupnp->sip_udp); + global_list = ms_list_append(global_list, lupnp->sip_udp); } if(lupnp->sip_tcp != NULL) { - upnp_port_binding_release(lupnp->sip_tcp); + global_list = ms_list_append(global_list, lupnp->sip_tcp); } if(lupnp->sip_tls != NULL) { - upnp_port_binding_release(lupnp->sip_tls); + global_list = ms_list_append(global_list, lupnp->sip_tls); } - if(lupnp->upnp_igd_ctxt != NULL) { - upnp_igd_destroy(lupnp->upnp_igd_ctxt); + + list = lupnp->lc->calls; + while(list != NULL) { + call = (LinphoneCall *)list->data; + if(call->upnp_session != NULL) { + if(call->upnp_session->audio->rtp != NULL) { + global_list = ms_list_append(global_list, call->upnp_session->audio->rtp); + } + if(call->upnp_session->audio->rtcp != NULL) { + global_list = ms_list_append(global_list, call->upnp_session->audio->rtcp); + } + if(call->upnp_session->video->rtp != NULL) { + global_list = ms_list_append(global_list, call->upnp_session->video->rtp); + } + if(call->upnp_session->video->rtcp != NULL) { + global_list = ms_list_append(global_list, call->upnp_session->video->rtcp); + } + } + list = list->next; } - ms_mutex_destroy(&lupnp->mutex); + + list = linphone_upnp_config_list_port_bindings(lupnp->lc->config, upnp_igd_get_device_id(lupnp->upnp_igd_ctxt)); + for(item = list;item != NULL; item = item->next) { + port_mapping = (UpnpPortBinding *)item->data; + port_mapping2 = linphone_upnp_port_binding_equivalent_in_list(global_list, port_mapping); + if(port_mapping2 == NULL) { + linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE); + } else if(port_mapping2->state == LinphoneUpnpStateIdle){ + /* Force to remove */ + port_mapping2->state = LinphoneUpnpStateOk; + } + } + ms_list_for_each(list, (void (*)(void*))linphone_upnp_port_binding_release); + list = ms_list_free(list); + + + // (Re)Add removed port bindings + list = global_list; + while(list != NULL) { + port_mapping = (UpnpPortBinding *)list->data; + linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE); + linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, TRUE); + list = list->next; + } + global_list = ms_list_free(global_list); } -UpnpPortBinding *upnp_port_binding_new() { +void linphone_upnp_update_port_binding(UpnpContext *lupnp, UpnpPortBinding **port_mapping, upnp_igd_ip_protocol protocol, int port, int retry_delay) { + const char *local_addr, *external_addr; + time_t now = time(NULL); + if(port != 0) { + if(*port_mapping != NULL) { + if(port != (*port_mapping)->local_port) { + linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE); + *port_mapping = NULL; + } + } + if(*port_mapping == NULL) { + *port_mapping = linphone_upnp_port_binding_new_or_collect(lupnp->pending_bindings, protocol, port, port); + } + + // Get addresses + local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt); + external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt); + + // Force binding update on local address change + if(local_addr != NULL) { + if(strncmp((*port_mapping)->local_addr, local_addr, sizeof((*port_mapping)->local_addr))) { + linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE); + strncpy((*port_mapping)->local_addr, local_addr, sizeof((*port_mapping)->local_addr)); + } + } + if(external_addr != NULL) { + strncpy((*port_mapping)->external_addr, external_addr, sizeof((*port_mapping)->external_addr)); + } + + // Add (if not already done) the binding + if(now - (*port_mapping)->last_update >= retry_delay) { + (*port_mapping)->last_update = now; + linphone_upnp_context_send_add_port_binding(lupnp, *port_mapping, FALSE); + } + } else { + if(*port_mapping != NULL) { + linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE); + *port_mapping = NULL; + } + } +} + +void linphone_upnp_update_config(UpnpContext* lupnp) { + char key[64]; + const MSList *item; + UpnpPortBinding *port_mapping; + + /* Add configs */ + for(item = lupnp->adding_configs;item!=NULL;item=item->next) { + port_mapping = (UpnpPortBinding *)item->data; + snprintf(key, sizeof(key), "%s-%s-%d-%d", + port_mapping->device_id, + (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP", + port_mapping->external_port, + port_mapping->local_port); + lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, "uPnP"); + linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping); + } + ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release); + lupnp->adding_configs = ms_list_free(lupnp->adding_configs); + + /* Remove configs */ + for(item = lupnp->removing_configs;item!=NULL;item=item->next) { + port_mapping = (UpnpPortBinding *)item->data; + snprintf(key, sizeof(key), "%s-%s-%d-%d", + port_mapping->device_id, + (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP", + port_mapping->external_port, + port_mapping->local_port); + lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, NULL); + linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping); + } + ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release); + lupnp->removing_configs = ms_list_free(lupnp->removing_configs); +} + +void linphone_upnp_update_proxy(UpnpContext* lupnp, bool_t force) { + LinphoneUpnpState ready_state; + const MSList *item; + time_t now = (force)? (lupnp->last_ready_check + UPNP_CORE_READY_CHECK) : time(NULL); + + /* Refresh registers if we are ready */ + if(now - lupnp->last_ready_check >= UPNP_CORE_READY_CHECK) { + lupnp->last_ready_check = now; + ready_state = (_linphone_upnp_context_is_ready_for_register(lupnp))? LinphoneUpnpStateOk: LinphoneUpnpStateKo; + if(ready_state != lupnp->last_ready_state) { + for(item=linphone_core_get_proxy_config_list(lupnp->lc);item!=NULL;item=item->next) { + LinphoneProxyConfig *cfg=(LinphoneProxyConfig*)item->data; + if (linphone_proxy_config_register_enabled(cfg)) { + if (ready_state != LinphoneUpnpStateOk) { + // Only reset ithe registration if we require that upnp should be ok + if(lupnp->lc->sip_conf.register_only_when_upnp_is_ok) { + linphone_proxy_config_set_state(cfg, LinphoneRegistrationNone, "Registration impossible (uPnP not ready)"); + } else { + cfg->commit=TRUE; + } + } else { + cfg->commit=TRUE; + } + } + } + lupnp->last_ready_state = ready_state; + } + } +} + +bool_t linphone_core_upnp_hook(void *data) { + LCSipTransports transport; + UpnpContext *lupnp = (UpnpContext *)data; + + ms_mutex_lock(&lupnp->mutex); + + /* Update ports */ + if(lupnp->state == LinphoneUpnpStateOk) { + linphone_core_get_sip_transports(lupnp->lc, &transport); + linphone_upnp_update_port_binding(lupnp, &lupnp->sip_udp, UPNP_IGD_IP_PROTOCOL_UDP, transport.udp_port, UPNP_CORE_RETRY_DELAY); + linphone_upnp_update_port_binding(lupnp, &lupnp->sip_tcp, UPNP_IGD_IP_PROTOCOL_TCP, transport.tcp_port, UPNP_CORE_RETRY_DELAY); + linphone_upnp_update_port_binding(lupnp, &lupnp->sip_tls, UPNP_IGD_IP_PROTOCOL_TCP, transport.tls_port, UPNP_CORE_RETRY_DELAY); + } + + linphone_upnp_update_proxy(lupnp, FALSE); + linphone_upnp_update_config(lupnp); + + ms_mutex_unlock(&lupnp->mutex); + return TRUE; +} + +int linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) { + int i; + SalStreamDescription *stream; + UpnpStream *upnpStream; + + for (i = 0; i < desc->n_active_streams; 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 != NULL && 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 != NULL && upnpStream->rtcp->state == LinphoneUpnpStateOk) { + strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE); + stream->rtcp_port = upnpStream->rtcp->external_port; + } + } + } + return 0; +} + + +/* + * uPnP Port Binding + */ + +UpnpPortBinding *linphone_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->protocol = UPNP_IGD_IP_PROTOCOL_UDP; + port->device_id = NULL; + port->local_addr[0] = '\0'; port->local_port = -1; - port->remote_port = -1; + port->external_addr[0] = '\0'; + port->external_port = -1; + port->to_remove = FALSE; + port->to_add = FALSE; port->ref = 1; + port->last_update = 0; return port; } -UpnpPortBinding *upnp_port_binding_retain(UpnpPortBinding *port) { +UpnpPortBinding *linphone_upnp_port_binding_new_with_parameters(upnp_igd_ip_protocol protocol, int local_port, int external_port) { + UpnpPortBinding *port_binding = linphone_upnp_port_binding_new(); + port_binding->protocol = protocol; + port_binding->local_port = local_port; + port_binding->external_port = external_port; + return port_binding; +} + +UpnpPortBinding *linphone_upnp_port_binding_new_or_collect(MSList *list, upnp_igd_ip_protocol protocol, int local_port, int external_port) { + UpnpPortBinding *tmp_binding; + UpnpPortBinding *end_binding; + + // Seek an binding with same protocol and local port + end_binding = linphone_upnp_port_binding_new_with_parameters(protocol, local_port, -1); + tmp_binding = linphone_upnp_port_binding_equivalent_in_list(list, end_binding); + + // Must be not attached to any struct + if(tmp_binding != NULL && tmp_binding->ref == 1) { + linphone_upnp_port_binding_release(end_binding); + end_binding = linphone_upnp_port_binding_retain(tmp_binding); + } else { + end_binding->external_port = external_port; + } + return end_binding; +} + +UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port) { + UpnpPortBinding *new_port = NULL; + new_port = ms_new0(UpnpPortBinding,1); + memcpy(new_port, port, sizeof(UpnpPortBinding)); + new_port->device_id = NULL; + linphone_upnp_port_binding_set_device_id(new_port, port->device_id); + ms_mutex_init(&new_port->mutex, NULL); + new_port->ref = 1; + return new_port; +} + +void linphone_upnp_port_binding_set_device_id(UpnpPortBinding *port, const char *device_id) { + char *formated_device_id = linphone_upnp_format_device_id(device_id); + if(formated_device_id != NULL && port->device_id != NULL) { + if(strcmp(formated_device_id, port->device_id) == 0) { + ms_free(formated_device_id); + return; + } + } + if(port->device_id != NULL) { + ms_free(port->device_id); + } + port->device_id = formated_device_id; +} + +void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port) { + if(strlen(port->local_addr)) { + ortp_log(level, "uPnP IGD: %s %s|%d->%s:%d (retry %d)", msg, + (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP", + port->external_port, + port->local_addr, + port->local_port, + port->retry - 1); + } else { + ortp_log(level, "uPnP IGD: %s %s|%d->%d (retry %d)", msg, + (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP", + port->external_port, + port->local_port, + port->retry - 1); + } +} + +// Return true if the binding are equivalent. (Note external_port == -1 means "don't care") +bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) { + return port1->protocol == port2->protocol && + port1->local_port == port2->local_port && + (port1->external_port == -1 || port2->external_port == -1 || port1->external_port == port2->external_port); +} + +UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port) { + UpnpPortBinding *port_mapping; + while(list != NULL) { + port_mapping = (UpnpPortBinding *)list->data; + if(linphone_upnp_port_binding_equal(port, port_mapping)) { + return port_mapping; + } + list = list->next; + } + + return NULL; +} + +UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port) { ms_mutex_lock(&port->mutex); port->ref++; ms_mutex_unlock(&port->mutex); return port; } -void upnp_port_binding_release(UpnpPortBinding *port) { +void linphone_upnp_port_binding_release(UpnpPortBinding *port) { ms_mutex_lock(&port->mutex); if(--port->ref == 0) { + if(port->device_id != NULL) { + ms_free(port->device_id); + } ms_mutex_unlock(&port->mutex); ms_mutex_destroy(&port->mutex); ms_free(port); @@ -318,25 +1151,197 @@ void upnp_port_binding_release(UpnpPortBinding *port) { ms_mutex_unlock(&port->mutex); } -UpnpSession* upnp_session_new() { + +/* + * uPnP Stream + */ + +UpnpStream* linphone_upnp_stream_new() { + UpnpStream *stream = ms_new0(UpnpStream,1); + stream->state = LinphoneUpnpStateIdle; + stream->rtp = NULL; + stream->rtcp = NULL; + return stream; +} + +void linphone_upnp_stream_destroy(UpnpStream* stream) { + if(stream->rtp != NULL) { + linphone_upnp_port_binding_release(stream->rtp); + stream->rtp = NULL; + } + if(stream->rtcp != NULL) { + linphone_upnp_port_binding_release(stream->rtcp); + stream->rtcp = NULL; + } + ms_free(stream); +} + + +/* + * uPnP Session + */ + +UpnpSession* linphone_upnp_session_new(LinphoneCall* call) { 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; - return NULL; + session->call = call; + session->state = LinphoneUpnpStateIdle; + session->audio = linphone_upnp_stream_new(); + session->video = linphone_upnp_stream_new(); + return session; } -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 +void linphone_upnp_session_destroy(UpnpSession *session) { + LinphoneCore *lc = session->call->core; + + if(lc->upnp != NULL) { + /* Remove bindings */ + if(session->audio->rtp != NULL) { + linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtp, TRUE); + } + if(session->audio->rtcp != NULL) { + linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtcp, TRUE); + } + if(session->video->rtp != NULL) { + linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtp, TRUE); + } + if(session->video->rtcp != NULL) { + linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtcp, TRUE); + } + } + + session->call->stats[LINPHONE_CALL_STATS_AUDIO].upnp_state = LinphoneUpnpStateKo; + session->call->stats[LINPHONE_CALL_STATS_VIDEO].upnp_state = LinphoneUpnpStateKo; + + linphone_upnp_stream_destroy(session->audio); + linphone_upnp_stream_destroy(session->video); ms_free(session); } + +LinphoneUpnpState linphone_upnp_session_get_state(UpnpSession *session) { + return session->state; +} + + +/* + * uPnP Config + */ + +struct linphone_upnp_config_list_port_bindings_struct { + struct _LpConfig *lpc; + MSList *retList; + const char *device_id; +}; + +static void linphone_upnp_config_list_port_bindings_cb(const char *entry, struct linphone_upnp_config_list_port_bindings_struct *cookie) { + char device_id[UPNP_UUID_LEN + 1]; + char protocol_str[4]; // TCP or UDP + upnp_igd_ip_protocol protocol; + int external_port; + int local_port; + int ret; + bool_t valid = TRUE; + UpnpPortBinding *port; + + ret = sscanf(entry, "%"UPNP_UUID_LEN_STR"[^-]-%3s-%i-%i", device_id, protocol_str, &external_port, &local_port); + if(ret == 4) { + // Handle only wanted device bindings + if(device_id != NULL && strcmp(cookie->device_id, device_id) != 0) { + return; + } + if(linphone_upnp_strncmpi(protocol_str, "TCP", 3) == 0) { + protocol = UPNP_IGD_IP_PROTOCOL_TCP; + } else if(linphone_upnp_strncmpi(protocol_str, "UDP", 3) == 0) { + protocol = UPNP_IGD_IP_PROTOCOL_UDP; + } else { + valid = FALSE; + } + if(valid) { + port = linphone_upnp_port_binding_new(); + linphone_upnp_port_binding_set_device_id(port, device_id); + port->state = LinphoneUpnpStateOk; + port->protocol = protocol; + port->external_port = external_port; + port->local_port = local_port; + cookie->retList = ms_list_append(cookie->retList, port); + } + } else { + valid = FALSE; + } + if(!valid) { + ms_warning("uPnP configuration invalid line: %s", entry); + } +} + +MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc, const char *device_id) { + char *formated_device_id = linphone_upnp_format_device_id(device_id); + struct linphone_upnp_config_list_port_bindings_struct cookie = {lpc, NULL, formated_device_id}; + lp_config_for_each_entry(lpc, UPNP_SECTION_NAME, (void(*)(const char *, void*))linphone_upnp_config_list_port_bindings_cb, &cookie); + ms_free(formated_device_id); + return cookie.retList; +} + +void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) { + MSList *list; + UpnpPortBinding *list_port; + + if(port->device_id == NULL) { + ms_error("Can't remove port binding without device_id"); + return; + } + + list = lupnp->removing_configs; + while(list != NULL) { + list_port = (UpnpPortBinding *)list->data; + if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) { + lupnp->removing_configs = ms_list_remove(lupnp->removing_configs, list_port); + linphone_upnp_port_binding_release(list_port); + return; + } + list = ms_list_next(list); + } + + list = lupnp->adding_configs; + while(list != NULL) { + list_port = (UpnpPortBinding *)list->data; + if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) { + return; + } + list = ms_list_next(list); + } + + list_port = linphone_upnp_port_binding_copy(port); + lupnp->adding_configs = ms_list_append(lupnp->adding_configs, list_port); +} + +void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) { + MSList *list; + UpnpPortBinding *list_port; + + if(port->device_id == NULL) { + ms_error("Can't remove port binding without device_id"); + return; + } + + list = lupnp->adding_configs; + while(list != NULL) { + list_port = (UpnpPortBinding *)list->data; + if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) { + lupnp->adding_configs = ms_list_remove(lupnp->adding_configs, list_port); + linphone_upnp_port_binding_release(list_port); + return; + } + list = ms_list_next(list); + } + + list = lupnp->removing_configs; + while(list != NULL) { + list_port = (UpnpPortBinding *)list->data; + if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) { + return; + } + list = ms_list_next(list); + } + + list_port = linphone_upnp_port_binding_copy(port); + lupnp->removing_configs = ms_list_append(lupnp->removing_configs, list_port); +}