X-Git-Url: http://sjero.net/git/?a=blobdiff_plain;f=coreapi%2Fupnp.c;h=0922dad59eb7aa7c66d8f7517da589715f2c1422;hb=9b7ac9b79375f845edd0e8366e602863a223d2c0;hp=4cb41d7ae09d2f1c4f1f0533f8ee3d1157f7529f;hpb=f3805137e6c2f4821450ed6391ebc92a727ec7fd;p=linphone diff --git a/coreapi/upnp.c b/coreapi/upnp.c index 4cb41d7a..0922dad5 100644 --- a/coreapi/upnp.c +++ b/coreapi/upnp.c @@ -19,50 +19,154 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "upnp.h" #include "private.h" +#include "lpconfig.h" +#include + +#define UPNP_STRINGIFY(x) #x +#define UPNP_TOSTRING(x) UPNP_STRINGIFY(x) + +#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 + */ -#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); +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; +}; -UpnpPortBinding *upnp_port_binding_new(); -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_log(int level, const char *msg, const 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); +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; +} -int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *port); -int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port); +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 @@ -88,16 +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: @@ -114,6 +227,9 @@ void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) { } else { ms_message("uPnP IGD: Connected"); lupnp->state = LinphoneUpnpStateOk; + if(old_state != LinphoneUpnpStateOk) { + linphone_core_upnp_refresh(lupnp); + } } break; @@ -123,47 +239,73 @@ void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) { port_mapping = (UpnpPortBinding*) mapping->cookie; port_mapping->external_port = mapping->remote_port; port_mapping->state = LinphoneUpnpStateOk; - upnp_port_binding_log(ORTP_MESSAGE, "Added port binding", port_mapping); - upnp_config_add_port_binding(lc, port_mapping); + linphone_upnp_port_binding_log(ORTP_MESSAGE, "Added port binding", port_mapping); + linphone_upnp_config_add_port_binding(lupnp, 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; - if(upnp_context_send_add_port_binding(lc, port_mapping) != 0) { - upnp_port_binding_log(ORTP_ERROR, "Can't add port binding", 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); } - 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->state = LinphoneUpnpStateIdle; - upnp_port_binding_log(ORTP_MESSAGE, "Removed port binding", port_mapping); - upnp_config_remove_port_binding(lc, port_mapping); + linphone_upnp_port_binding_log(ORTP_MESSAGE, "Removed port binding", port_mapping); + linphone_upnp_config_remove_port_binding(lupnp, 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; - if(upnp_context_send_remove_port_binding(lc, port_mapping) != 0) { - upnp_port_binding_log(ORTP_ERROR, "Can't remove port binding", port_mapping); - upnp_config_remove_port_binding(lc, 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); } - upnp_port_binding_release(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); } @@ -172,114 +314,257 @@ void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) { * uPnP Context */ -int upnp_context_init(LinphoneCore *lc) { - LCSipTransports transport; - UpnpContext *lupnp = &lc->upnp; - const char *ip_address; +UpnpContext* linphone_upnp_context_new(LinphoneCore *lc) { + UpnpContext *lupnp = (UpnpContext *)ms_new0(UpnpContext,1); ms_mutex_init(&lupnp->mutex, NULL); - lupnp->pending_configs = 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; - 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; - } + 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, lc); + 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, lc); + 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 -1; + return NULL; } - ip_address = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt); + 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) { - strncpy(lupnp->sip_udp->local_addr, ip_address, sizeof(lupnp->sip_udp->local_addr)); + linphone_upnp_port_binding_release(lupnp->sip_udp); + lupnp->sip_udp = NULL; } if(lupnp->sip_tcp != NULL) { - strncpy(lupnp->sip_tcp->local_addr, ip_address, sizeof(lupnp->sip_tcp->local_addr)); + linphone_upnp_port_binding_release(lupnp->sip_tcp); + lupnp->sip_tcp = NULL; } if(lupnp->sip_tls != NULL) { - strncpy(lupnp->sip_tls->local_addr, ip_address, sizeof(lupnp->sip_tls->local_addr)); + linphone_upnp_port_binding_release(lupnp->sip_tls); + lupnp->sip_tcp = NULL; } - lupnp->state = LinphoneUpnpStatePending; - return 0; + /* 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); } -void upnp_context_uninit(LinphoneCore *lc) { - UpnpContext *lupnp = &lc->upnp; - linphone_core_remove_iterate_hook(lc, linphone_core_upnp_hook, lc); +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; +} - if(lupnp->sip_udp != NULL) { - upnp_port_binding_release(lupnp->sip_udp); +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; + } } - if(lupnp->sip_tcp != NULL) { - upnp_port_binding_release(lupnp->sip_tcp); + + // 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; + } } - if(lupnp->sip_tls != NULL) { - upnp_port_binding_release(lupnp->sip_tls); + + 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); } - if(lupnp->upnp_igd_ctxt != NULL) { - upnp_igd_destroy(lupnp->upnp_igd_ctxt); + 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); } - ms_mutex_destroy(&lupnp->mutex); + return port; +} + +void linphone_upnp_refresh(UpnpContext * lupnp) { + upnp_igd_refresh(lupnp->upnp_igd_ctxt); +} - ms_message("uPnP IGD: Uninit"); +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 upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port) { - UpnpContext *lupnp = &lc->upnp; +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(port->state == LinphoneUpnpStateIdle) { - port->external_port = -1; - port->retry = 0; - port->state = LinphoneUpnpStateAdding; - } else if(port->state != LinphoneUpnpStateAdding) { - ms_error("uPnP: try to add a port binding in wrong state: %d", port->state); + + 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 { - 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 = 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; + 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(ret != 0) { @@ -288,26 +573,50 @@ int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port) 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 == LinphoneUpnpStateOk) { - port->retry = 0; - port->state = LinphoneUpnpStateRemoving; - } else if(port->state != LinphoneUpnpStateRemoving) { - ms_error("uPnP: try to remove a port binding in wrong state: %d", port->state); + + if(lupnp->state != LinphoneUpnpStateOk) { return -2; } - if(port->retry >= UPNP_MAX_RETRY) { + // 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 { - 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.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) { @@ -322,78 +631,57 @@ int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *por 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; - const char *local_addr, *external_addr; + + 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; - local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt); - external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt); /* * Audio part */ - 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 && audio) { - // Add audio port binding - upnp_context_send_add_port_binding(lc, call->upnp_session->audio->rtp); - } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk && !audio) { - // Remove audio port binding - upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtp); - } - if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle && audio) { - // Add audio port binding - upnp_context_send_add_port_binding(lc, call->upnp_session->audio->rtcp); - } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk && !audio) { - // 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 */ - 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 && video) { - // Add video port binding - upnp_context_send_add_port_binding(lc, call->upnp_session->video->rtp); - } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateOk && !video) { - // Remove video port binding - upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtp); - } - if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle && video) { - // Add video port binding - upnp_context_send_add_port_binding(lc, call->upnp_session->video->rtcp); - } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk && !video) { - // 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->nstreams; i++) { + for (i = 0; i < md->n_total_streams; i++) { stream = &md->streams[i]; if(stream->type == SalAudio) { audio = TRUE; @@ -409,11 +697,37 @@ int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) { return linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL); } -int upnp_call_process(LinphoneCall *call) { +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; +} + +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 { + ms_error("Invalid stream %p state", stream); + } +} + +int linphone_upnp_call_process(LinphoneCall *call) { LinphoneCore *lc = call->core; - UpnpContext *lupnp = &lc->upnp; + UpnpContext *lupnp = lc->upnp; int ret = -1; - UpnpState oldState; + LinphoneUpnpState oldState = 0, newState = 0; + + if(lupnp == NULL) { + return ret; + } ms_mutex_lock(&lupnp->mutex); @@ -424,39 +738,18 @@ int upnp_call_process(LinphoneCall *call) { /* * Update Audio state */ - 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; - } + linphone_upnp_update_stream_state(call->upnp_session->audio); /* * Update Video state */ - 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; - } + linphone_upnp_update_stream_state(call->upnp_session->video); + /* + * Update stat + */ + linphone_core_update_upnp_state_in_call_stats(call); + /* * Update session state */ @@ -473,105 +766,238 @@ int upnp_call_process(LinphoneCall *call) { } else { call->upnp_session->state = LinphoneUpnpStateIdle; } + newState = call->upnp_session->state; + } - /* When change is done proceed update */ - if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo && - (call->upnp_session->state == LinphoneUpnpStateOk || call->upnp_session->state == 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; - } + 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; } } - ms_mutex_unlock(&lupnp->mutex); return ret; } -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_lock(&lupnp->mutex); +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->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); + if(lupnp->sip_udp != NULL) { + global_list = ms_list_append(global_list, lupnp->sip_udp); + } + if(lupnp->sip_tcp != NULL) { + global_list = ms_list_append(global_list, lupnp->sip_tcp); + } + if(lupnp->sip_tls != NULL) { + global_list = ms_list_append(global_list, lupnp->sip_tls); + } + + 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); } - ms_list_for_each(port_bindings,(void (*)(void*))upnp_port_binding_release); - port_bindings = ms_list_free(port_bindings); } + list = list->next; } - 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); + 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); +} + +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(lupnp->sip_tcp != NULL) { - upnp_context_send_add_port_binding(lc, lupnp->sip_tcp); + if(*port_mapping == NULL) { + *port_mapping = linphone_upnp_port_binding_new_or_collect(lupnp->pending_bindings, protocol, port, port); } - if(lupnp->sip_tls != NULL) { - upnp_context_send_add_port_binding(lc, lupnp->sip_tls); + + // 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)); } - } - /* 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, "uPnP"); + // 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); } - 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); + } else { + if(*port_mapping != NULL) { + linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE); + *port_mapping = NULL; } } - ms_list_for_each(lupnp->pending_configs,(void (*)(void*))upnp_port_binding_release); - lupnp->pending_configs = ms_list_free(lupnp->pending_configs); +} + +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); - lupnp->old_state = lupnp->state; ms_mutex_unlock(&lupnp->mutex); return TRUE; } -void linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) { +int 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++) { + for (i = 0; i < desc->n_active_streams; i++) { stream = &desc->streams[i]; upnpStream = NULL; if(stream->type == SalAudio) { @@ -580,16 +1006,17 @@ void linphone_core_update_local_media_description_from_upnp(SalMediaDescription upnpStream = session->video; } if(upnpStream != NULL) { - if(upnpStream->rtp->state == LinphoneUpnpStateOk) { + 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->state == LinphoneUpnpStateOk) { + 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; } @@ -597,58 +1024,125 @@ void linphone_core_update_local_media_description_from_upnp(SalMediaDescription * uPnP Port Binding */ -UpnpPortBinding *upnp_port_binding_new() { +UpnpPortBinding *linphone_upnp_port_binding_new() { UpnpPortBinding *port = NULL; port = ms_new0(UpnpPortBinding,1); ms_mutex_init(&port->mutex, NULL); port->state = LinphoneUpnpStateIdle; + port->protocol = UPNP_IGD_IP_PROTOCOL_UDP; + port->device_id = NULL; port->local_addr[0] = '\0'; port->local_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_copy(const 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 upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *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", msg, + 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->local_port, + port->retry - 1); } else { - ortp_log(level, "uPnP IGD: %s %s|%d->%d", msg, + 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->local_port, + port->retry - 1); } } -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; +// 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 *upnp_port_binding_retain(UpnpPortBinding *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); @@ -662,19 +1156,23 @@ void upnp_port_binding_release(UpnpPortBinding *port) { * uPnP Stream */ -UpnpStream* upnp_stream_new() { +UpnpStream* linphone_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; + stream->rtp = NULL; + stream->rtcp = NULL; return stream; } -void upnp_stream_destroy(UpnpStream* stream) { - upnp_port_binding_release(stream->rtp); - upnp_port_binding_release(stream->rtcp); +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); } @@ -683,35 +1181,44 @@ void upnp_stream_destroy(UpnpStream* stream) { * uPnP Session */ -UpnpSession* upnp_session_new() { +UpnpSession* linphone_upnp_session_new(LinphoneCall* call) { UpnpSession *session = ms_new0(UpnpSession,1); + session->call = call; session->state = LinphoneUpnpStateIdle; - session->audio = upnp_stream_new(); - session->video = upnp_stream_new(); + session->audio = linphone_upnp_stream_new(); + session->video = linphone_upnp_stream_new(); return session; } -void upnp_session_destroy(LinphoneCall* call) { - LinphoneCore *lc = call->core; +void linphone_upnp_session_destroy(UpnpSession *session) { + LinphoneCore *lc = session->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); + 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); +} - upnp_stream_destroy(call->upnp_session->audio); - upnp_stream_destroy(call->upnp_session->video); - ms_free(call->upnp_session); - call->upnp_session = NULL; +LinphoneUpnpState linphone_upnp_session_get_state(UpnpSession *session) { + return session->state; } @@ -719,117 +1226,122 @@ void upnp_session_destroy(LinphoneCall* call) { * uPnP Config */ -MSList *upnp_config_list_port_bindings(struct _LpConfig *lpc) { +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; - MSList *retList = NULL; + int ret; + bool_t valid = TRUE; 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->state = LinphoneUpnpStateOk; - port->protocol = protocol; - port->external_port = external_port; - port->local_port = local_port; - retList = ms_list_append(retList, 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; } - 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); + 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); + } +} - return retList; +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; } -int upnp_config_add_port_binding(LinphoneCore *lc, const UpnpPortBinding *port) { - UpnpContext *lupnp = &lc->upnp; - MSList *list = lupnp->pending_configs; +void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) { + MSList *list; UpnpPortBinding *list_port; - bool_t remove; - bool_t add = TRUE; + + if(port->device_id == NULL) { + ms_error("Can't remove port binding without device_id"); + return; + } + + list = lupnp->removing_configs; 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; - } + 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); } - 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); + 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); } - return 0; + list_port = linphone_upnp_port_binding_copy(port); + lupnp->adding_configs = ms_list_append(lupnp->adding_configs, list_port); } -int upnp_config_remove_port_binding(LinphoneCore *lc, const UpnpPortBinding *port) { - UpnpContext *lupnp = &lc->upnp; - MSList *list = lupnp->pending_configs; +void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) { + MSList *list; UpnpPortBinding *list_port; - bool_t remove; - bool_t add = TRUE; + + if(port->device_id == NULL) { + ms_error("Can't remove port binding without device_id"); + return; + } + + list = lupnp->adding_configs; 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; - } + 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); } - 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); + 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); } - return 0; + list_port = linphone_upnp_port_binding_copy(port); + lupnp->removing_configs = ms_list_append(lupnp->removing_configs, list_port); }