X-Git-Url: http://sjero.net/git/?a=blobdiff_plain;f=coreapi%2Fupnp.c;h=0922dad59eb7aa7c66d8f7517da589715f2c1422;hb=9b7ac9b79375f845edd0e8366e602863a223d2c0;hp=a05e1e0f2f0c3b93e25d4fe4f47a576df2478888;hpb=8fed4df37f8c49addf3607fd5d6df829da0614fd;p=linphone diff --git a/coreapi/upnp.c b/coreapi/upnp.c index a05e1e0f..0922dad5 100644 --- a/coreapi/upnp.c +++ b/coreapi/upnp.c @@ -20,11 +20,19 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "upnp.h" #include "private.h" #include "lpconfig.h" +#include -#define UPNP_ADD_MAX_RETRY 4 -#define UPNP_REMOVE_MAX_RETRY 4 -#define UPNP_SECTION_NAME "uPnP" +#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 */ @@ -33,6 +41,7 @@ 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]; @@ -41,6 +50,7 @@ typedef struct _UpnpPortBinding { int ref; bool_t to_remove; bool_t to_add; + time_t last_update; } UpnpPortBinding; typedef struct _UpnpStream { @@ -69,7 +79,9 @@ struct _UpnpContext { ms_mutex_t mutex; ms_cond_t empty_cond; - + + time_t last_ready_check; + LinphoneUpnpState last_ready_state; }; @@ -77,20 +89,84 @@ 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); -MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc); +// 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); -int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port); -int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, 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 @@ -133,6 +209,8 @@ void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) { 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: @@ -170,7 +248,7 @@ void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) { mapping = (upnp_igd_port_mapping *) arg; port_mapping = (UpnpPortBinding*) mapping->cookie; port_mapping->external_port = -1; //Force random external port - if(linphone_upnp_context_send_add_port_binding(lupnp, port_mapping) != 0) { + 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); } @@ -188,7 +266,7 @@ void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) { case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE: mapping = (upnp_igd_port_mapping *) arg; port_mapping = (UpnpPortBinding*) mapping->cookie; - if(linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping) != 0) { + 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); } @@ -206,7 +284,7 @@ void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) { 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); + linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, FALSE); } else if(port_mapping->state == LinphoneUpnpStateKo) { port_mapping->to_remove = FALSE; } @@ -214,7 +292,7 @@ void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) { 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); + linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, FALSE); } } @@ -226,7 +304,7 @@ void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) { * If there is no pending binding emit a signal */ if(lupnp->pending_bindings == NULL) { - pthread_cond_signal(&lupnp->empty_cond); + ms_cond_signal(&lupnp->empty_cond); } ms_mutex_unlock(&lupnp->mutex); } @@ -237,13 +315,14 @@ void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) { */ UpnpContext* linphone_upnp_context_new(LinphoneCore *lc) { - LCSipTransports transport; UpnpContext *lupnp = (UpnpContext *)ms_new0(UpnpContext,1); - const char *ip_address; 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; @@ -251,53 +330,21 @@ UpnpContext* linphone_upnp_context_new(LinphoneCore *lc) { lupnp->state = LinphoneUpnpStateIdle; ms_message("uPnP IGD: New %p for core %p", lupnp, lc); - linphone_core_get_sip_transports(lc, &transport); - if(transport.udp_port != 0) { - lupnp->sip_udp = linphone_upnp_port_binding_new(); - lupnp->sip_udp->protocol = UPNP_IGD_IP_PROTOCOL_UDP; - lupnp->sip_udp->local_port = transport.udp_port; - lupnp->sip_udp->external_port = transport.udp_port; - } else { - lupnp->sip_udp = NULL; - } - if(transport.tcp_port != 0) { - lupnp->sip_tcp = linphone_upnp_port_binding_new(); - lupnp->sip_tcp->protocol = UPNP_IGD_IP_PROTOCOL_TCP; - lupnp->sip_tcp->local_port = transport.tcp_port; - lupnp->sip_tcp->external_port = transport.tcp_port; - } else { - lupnp->sip_tcp = NULL; - } - if(transport.tls_port != 0) { - lupnp->sip_tls = linphone_upnp_port_binding_new(); - lupnp->sip_tls->protocol = UPNP_IGD_IP_PROTOCOL_TCP; - lupnp->sip_tls->local_port = transport.tls_port; - lupnp->sip_tls->external_port = transport.tls_port; - } else { - lupnp->sip_tls = NULL; - } + // 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, lupnp); + 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; } - ip_address = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt); - if(lupnp->sip_udp != NULL) { - strncpy(lupnp->sip_udp->local_addr, ip_address, sizeof(lupnp->sip_udp->local_addr)); - } - if(lupnp->sip_tcp != NULL) { - strncpy(lupnp->sip_tcp->local_addr, ip_address, sizeof(lupnp->sip_tcp->local_addr)); - } - if(lupnp->sip_tls != NULL) { - strncpy(lupnp->sip_tls->local_addr, ip_address, sizeof(lupnp->sip_tls->local_addr)); - } - lupnp->state = LinphoneUpnpStatePending; upnp_igd_start(lupnp->upnp_igd_ctxt); @@ -305,25 +352,21 @@ UpnpContext* linphone_upnp_context_new(LinphoneCore *lc) { } void linphone_upnp_context_destroy(UpnpContext *lupnp) { - /* - * Not need, all hooks are removed before - * linphone_core_remove_iterate_hook(lc, linphone_core_upnp_hook, lc); - */ + linphone_core_remove_iterate_hook(lupnp->lc, linphone_core_upnp_hook, lupnp); ms_mutex_lock(&lupnp->mutex); - /* Send port binding removes */ - if(lupnp->sip_udp != NULL) { - linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_udp); - lupnp->sip_udp = NULL; - } - if(lupnp->sip_tcp != NULL) { - linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tcp); - lupnp->sip_tcp = NULL; - } - if(lupnp->sip_tls != NULL) { - linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tls); - lupnp->sip_tcp = NULL; + 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 */ @@ -335,10 +378,14 @@ void linphone_upnp_context_destroy(UpnpContext *lupnp) { 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 time the hook for configuration update */ - linphone_core_upnp_hook(lupnp); + /* 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) { @@ -361,7 +408,7 @@ void linphone_upnp_context_destroy(UpnpContext *lupnp) { 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); @@ -369,18 +416,107 @@ void linphone_upnp_context_destroy(UpnpContext *lupnp) { ms_free(lupnp); } -LinphoneUpnpState linphone_upnp_context_get_state(UpnpContext *ctx) { - return ctx->state; +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; } -const char* linphone_upnp_context_get_external_ipaddress(UpnpContext *ctx) { - return upnp_igd_get_external_ipaddress(ctx->upnp_igd_ctxt); +void linphone_upnp_refresh(UpnpContext * lupnp) { + upnp_igd_refresh(lupnp->upnp_igd_ctxt); } -int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port) { +const char* linphone_upnp_context_get_external_ipaddress(UpnpContext *lupnp) { + const char* addr = NULL; + if(lupnp != NULL) { + ms_mutex_lock(&lupnp->mutex); + addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt); + ms_mutex_unlock(&lupnp->mutex); + } + return addr; +} + +int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry) { upnp_igd_port_mapping mapping; char description[128]; int ret; + + if(lupnp->state != LinphoneUpnpStateOk) { + return -2; + } // Compute port binding state if(port->state != LinphoneUpnpStateAdding) { @@ -401,19 +537,24 @@ int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBind return 0; } } + + // No retry if specified + if(port->retry != 0 && !retry) { + return -1; + } if(port->retry >= UPNP_ADD_MAX_RETRY) { ret = -1; } else { + linphone_upnp_port_binding_set_device_id(port, upnp_igd_get_device_id(lupnp->upnp_igd_ctxt)); mapping.cookie = linphone_upnp_port_binding_retain(port); lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie); mapping.local_port = port->local_port; mapping.local_host = port->local_addr; if(port->external_port == -1) - mapping.remote_port = rand()%(0xffff - 1024) + 1024; - else - mapping.remote_port = port->external_port; + port->external_port = rand()%(0xffff - 1024) + 1024; + mapping.remote_port = port->external_port; mapping.remote_host = ""; snprintf(description, 128, "%s %s at %s:%d", PACKAGE_NAME, @@ -432,9 +573,13 @@ int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBind return ret; } -int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port) { +int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry) { upnp_igd_port_mapping mapping; int ret; + + if(lupnp->state != LinphoneUpnpStateOk) { + return -2; + } // Compute port binding state if(port->state != LinphoneUpnpStateRemoving) { @@ -454,10 +599,16 @@ int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortB return 0; } } + + // No retry if specified + if(port->retry != 0 && !retry) { + return 1; + } if(port->retry >= UPNP_REMOVE_MAX_RETRY) { ret = -1; } else { + linphone_upnp_port_binding_set_device_id(port, upnp_igd_get_device_id(lupnp->upnp_igd_ctxt)); mapping.cookie = linphone_upnp_port_binding_retain(port); lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie); @@ -482,68 +633,34 @@ int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool LinphoneCore *lc = call->core; 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; - if(call->upnp_session->audio->rtp->external_port == -1) { - call->upnp_session->audio->rtp->external_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->rtcp->external_port == -1) { - call->upnp_session->audio->rtcp->external_port = call->audio_port+1; - } - if(audio) { - // Add audio port binding - linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->audio->rtp); - linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->audio->rtcp); - } else { - // Remove audio port binding - linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->audio->rtp); - linphone_upnp_context_send_remove_port_binding(lupnp, 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; - if(call->upnp_session->video->rtp->external_port == -1) { - call->upnp_session->video->rtp->external_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->rtcp->external_port == -1) { - call->upnp_session->video->rtcp->external_port = call->video_port+1; - } - if(video) { - // Add video port binding - linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->video->rtp); - linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->video->rtcp); - } else { - // Remove video port binding - linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->video->rtp); - linphone_upnp_context_send_remove_port_binding(lupnp, 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); @@ -557,13 +674,14 @@ int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool } + 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; @@ -584,6 +702,23 @@ void linphone_core_update_upnp_state_in_call_stats(LinphoneCall *call) { 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; @@ -603,39 +738,18 @@ int linphone_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 */ @@ -653,41 +767,34 @@ int linphone_upnp_call_process(LinphoneCall *call) { 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); - - /* - * Update uPnP call stats - */ - if(oldState != newState) { - linphone_core_update_upnp_state_in_call_stats(call); + + /* 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; + } } return ret; @@ -702,7 +809,6 @@ void linphone_core_upnp_refresh(UpnpContext *lupnp) { ms_message("uPnP IGD: Refresh mappings"); - /* Remove context port bindings */ if(lupnp->sip_udp != NULL) { global_list = ms_list_append(global_list, lupnp->sip_udp); } @@ -713,26 +819,32 @@ void linphone_core_upnp_refresh(UpnpContext *lupnp) { global_list = ms_list_append(global_list, lupnp->sip_tls); } - /* Remove call port bindings */ list = lupnp->lc->calls; while(list != NULL) { call = (LinphoneCall *)list->data; if(call->upnp_session != NULL) { - global_list = ms_list_append(global_list, call->upnp_session->audio->rtp); - global_list = ms_list_append(global_list, call->upnp_session->audio->rtcp); - global_list = ms_list_append(global_list, call->upnp_session->video->rtp); - global_list = ms_list_append(global_list, call->upnp_session->video->rtcp); + 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; } - // Remove port binding configurations - list = linphone_upnp_config_list_port_bindings(lupnp->lc->config); + 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); + linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE); } else if(port_mapping2->state == LinphoneUpnpStateIdle){ /* Force to remove */ port_mapping2->state = LinphoneUpnpStateOk; @@ -746,27 +858,68 @@ void linphone_core_upnp_refresh(UpnpContext *lupnp) { list = global_list; while(list != NULL) { port_mapping = (UpnpPortBinding *)list->data; - linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping); - linphone_upnp_context_send_add_port_binding(lupnp, port_mapping); + 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); } -bool_t linphone_core_upnp_hook(void *data) { +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]; - MSList *item; + const MSList *item; UpnpPortBinding *port_mapping; - UpnpContext *lupnp = (UpnpContext *)data; - ms_mutex_lock(&lupnp->mutex); - + /* Add configs */ for(item = lupnp->adding_configs;item!=NULL;item=item->next) { port_mapping = (UpnpPortBinding *)item->data; - snprintf(key, sizeof(key), "%s-%d-%d", + 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); + 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); } @@ -776,15 +929,64 @@ bool_t linphone_core_upnp_hook(void *data) { /* Remove configs */ for(item = lupnp->removing_configs;item!=NULL;item=item->next) { port_mapping = (UpnpPortBinding *)item->data; - snprintf(key, sizeof(key), "%s-%d-%d", + 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); + 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; @@ -795,7 +997,7 @@ int linphone_core_update_local_media_description_from_upnp(SalMediaDescription * 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) { @@ -804,11 +1006,11 @@ int 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; } @@ -827,6 +1029,8 @@ UpnpPortBinding *linphone_upnp_port_binding_new() { 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'; @@ -834,37 +1038,83 @@ UpnpPortBinding *linphone_upnp_port_binding_new() { port->to_remove = FALSE; port->to_add = FALSE; port->ref = 1; + port->last_update = 0; return 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", 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); } } +// 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 == port2->external_port; + 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) { @@ -890,6 +1140,9 @@ UpnpPortBinding *linphone_upnp_port_binding_retain(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); @@ -906,18 +1159,20 @@ void linphone_upnp_port_binding_release(UpnpPortBinding *port) { UpnpStream* linphone_upnp_stream_new() { UpnpStream *stream = ms_new0(UpnpStream,1); stream->state = LinphoneUpnpStateIdle; - stream->rtp = linphone_upnp_port_binding_new(); - stream->rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP; - stream->rtcp = linphone_upnp_port_binding_new(); - stream->rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP; + stream->rtp = NULL; + stream->rtcp = NULL; return stream; } void linphone_upnp_stream_destroy(UpnpStream* stream) { - linphone_upnp_port_binding_release(stream->rtp); - stream->rtp = NULL; - linphone_upnp_port_binding_release(stream->rtcp); - stream->rtcp = NULL; + 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); } @@ -940,12 +1195,23 @@ void linphone_upnp_session_destroy(UpnpSession *session) { if(lc->upnp != NULL) { /* Remove bindings */ - linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtp); - linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtcp); - linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtp); - linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtcp); + 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); @@ -955,6 +1221,7 @@ LinphoneUpnpState linphone_upnp_session_get_state(UpnpSession *session) { return session->state; } + /* * uPnP Config */ @@ -962,25 +1229,35 @@ LinphoneUpnpState linphone_upnp_session_get_state(UpnpSession *session) { 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; - if(sscanf(entry, "%3s-%i-%i", protocol_str, &external_port, &local_port) == 3) { - if(strcasecmp(protocol_str, "TCP") == 0) { + + 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(strcasecmp(protocol_str, "UDP") == 0) { + } 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; @@ -995,15 +1272,22 @@ static void linphone_upnp_config_list_port_bindings_cb(const char *entry, struct } } -MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc) { - struct linphone_upnp_config_list_port_bindings_struct cookie = {lpc, NULL}; +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) { @@ -1033,6 +1317,11 @@ void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPort 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;