]> sjero.net Git - linphone/commitdiff
Working sip upnp
authorYann Diorcet <yann.diorcet@belledonne-communications.com>
Thu, 3 Jan 2013 14:38:03 +0000 (15:38 +0100)
committerYann Diorcet <yann.diorcet@belledonne-communications.com>
Thu, 3 Jan 2013 14:47:38 +0000 (15:47 +0100)
coreapi/callbacks.c
coreapi/linphonecall.c
coreapi/linphonecore.c
coreapi/private.h
coreapi/proxy.c
coreapi/upnp.c
coreapi/upnp.h
mediastreamer2

index 134c336abe3cc946715746477b40c9b35bbb7e93..b3d1a6bb7d2a0824e913f476c6378a4d0455aa83 100644 (file)
@@ -416,6 +416,11 @@ static void call_accept_update(LinphoneCore *lc, LinphoneCall *call){
                linphone_core_update_ice_from_remote_media_description(call,rmd);
                linphone_core_update_local_media_description_from_ice(call->localdesc,call->ice_session);
        }
+#ifdef BUILD_UPNP
+       if(call->upnp_session != NULL) {
+               linphone_core_update_local_media_description_from_upnp(call->localdesc,call->upnp_session);
+       }
+#endif
        sal_call_accept(call->op);
        md=sal_call_get_final_media_description(call->op);
        if (md && !sal_media_description_empty(md))
index 8af948df5f4f07399258e696a7b91e60185d9426..2ab398ba56ddb654179faabf43ddca5a14333e28 100644 (file)
@@ -283,6 +283,11 @@ void linphone_call_make_local_media_description(LinphoneCore *lc, LinphoneCall *
                linphone_core_update_local_media_description_from_ice(md, call->ice_session);
                linphone_core_update_ice_state_in_call_stats(call);
        }
+#ifdef BUILD_UPNP
+       if(call->upnp_session != NULL) {
+               linphone_core_update_local_media_description_from_upnp(md, call->upnp_session);
+       }
+#endif
        linphone_address_destroy(addr);
        call->localdesc=md;
        if (old_md) sal_media_description_unref(old_md);
@@ -439,7 +444,7 @@ LinphoneCall * linphone_call_new_outgoing(struct _LinphoneCore *lc, LinphoneAddr
        call->op=sal_op_new(lc->sal);
        sal_op_set_user_pointer(call->op,call);
        call->core=lc;
-       linphone_core_get_local_ip(lc,linphone_address_get_domain(to),call->localip);
+       linphone_core_get_public_ip(lc,linphone_address_get_domain(to),call->localip);
        linphone_call_init_common(call,from,to);
        call->params=*params;
        if (linphone_core_get_firewall_policy(call->core) == LinphonePolicyUseIce) {
@@ -486,7 +491,7 @@ LinphoneCall * linphone_call_new_incoming(LinphoneCore *lc, LinphoneAddress *fro
        }
 
        linphone_address_clean(from);
-       linphone_core_get_local_ip(lc,linphone_address_get_domain(from),call->localip);
+       linphone_core_get_public_ip(lc,linphone_address_get_domain(from),call->localip);
        linphone_call_init_common(call, from, to);
        call->log->call_id=ms_strdup(sal_op_get_call_id(op)); /*must be known at that time*/
        linphone_core_init_default_params(lc, &call->params);
@@ -1693,7 +1698,7 @@ void linphone_call_delete_ice_session(LinphoneCall *call){
 #ifdef BUILD_UPNP
 void linphone_call_delete_upnp_session(LinphoneCall *call){
        if(call->upnp_session!=NULL) {
-               upnp_session_destroy(call->upnp_session);
+               upnp_session_destroy(call);
                call->upnp_session=NULL;
        }
 }
index 82e39685f0290b34e950f0cf1cdc33c5e3ac671f..ddd9eaea6f02bdc5efd3b09d60a50826cbf951cc 100644 (file)
@@ -66,7 +66,6 @@ static void linphone_core_free_hooks(LinphoneCore *lc);
 
 #include "enum.h"
 const char *linphone_core_get_nat_address_resolved(LinphoneCore *lc);
-void linphone_core_get_local_ip(LinphoneCore *lc, const char *dest, char *result);
 static void toggle_video_preview(LinphoneCore *lc, bool_t val);
 
 /* relative path where is stored local ring*/
@@ -1307,13 +1306,20 @@ int linphone_core_set_primary_contact(LinphoneCore *lc, const char *contact)
 
 
 /*result must be an array of chars at least LINPHONE_IPADDR_SIZE */
-void linphone_core_get_local_ip(LinphoneCore *lc, const char *dest, char *result){
+void linphone_core_get_public_ip(LinphoneCore *lc, const char *dest, char *result){
        const char *ip;
        if (linphone_core_get_firewall_policy(lc)==LinphonePolicyUseNatAddress
            && (ip=linphone_core_get_nat_address_resolved(lc))!=NULL){
                strncpy(result,ip,LINPHONE_IPADDR_SIZE);
                return;
        }
+#ifdef BUILD_UPNP
+       else if (linphone_core_get_firewall_policy(lc)==LinphonePolicyUseUpnp
+               && lc->upnp.state == LinphoneUpnpStateOk) {
+               ip = upnp_igd_get_external_ipaddress(lc->upnp.upnp_igd_ctxt);
+               strncpy(result,ip,LINPHONE_IPADDR_SIZE);
+       }
+#endif
        if (linphone_core_get_local_ip_for(lc->sip_conf.ipv6_enabled ? AF_INET6 : AF_INET,dest,result)==0)
                return;
        /*else fallback to SAL routine that will attempt to find the most realistic interface */
@@ -1334,7 +1340,7 @@ static void update_primary_contact(LinphoneCore *lc){
                ms_error("Could not parse identity contact !");
                url=linphone_address_new("sip:unknown@unkwownhost");
        }
-       linphone_core_get_local_ip(lc, NULL, tmp);
+       linphone_core_get_public_ip(lc, NULL, tmp);
        if (strcmp(tmp,"127.0.0.1")==0 || strcmp(tmp,"::1")==0 ){
                ms_warning("Local loopback network only !");
                lc->sip_conf.loopback_only=TRUE;
@@ -2006,7 +2012,7 @@ void linphone_core_iterate(LinphoneCore *lc){
                                linphone_call_stop_media_streams_for_ice_gathering(call);
                        }
                        if (call->upnp_session != NULL) {
-                               ms_warning("uPnP mapping has not finished yet, proceeded with the call withoutt uPnP anyway.");
+                               ms_warning("uPnP mapping has not finished yet, proceeded with the call without uPnP anyway.");
                                linphone_call_delete_upnp_session(call);
                        }
                        linphone_core_start_invite(lc,call);
@@ -2639,9 +2645,14 @@ void linphone_core_notify_incoming_call(LinphoneCore *lc, LinphoneCall *call){
 int linphone_core_start_update_call(LinphoneCore *lc, LinphoneCall *call){
        const char *subject;
        call->camera_active=call->params.has_video;
-       if (call->ice_session != NULL)
+       if (call->ice_session != NULL) {
                linphone_core_update_local_media_description_from_ice(call->localdesc, call->ice_session);
-
+       }
+#ifdef BUILD_UPNP
+       if(call->upnp_session != NULL) {
+               linphone_core_update_local_media_description_from_upnp(call->localdesc, call->upnp_session);
+       }
+#endif
        if (call->params.in_conference){
                subject="Conference";
        }else{
@@ -2756,6 +2767,11 @@ int linphone_core_start_accept_call_update(LinphoneCore *lc, LinphoneCall *call)
                }
                linphone_core_update_local_media_description_from_ice(call->localdesc, call->ice_session);
        }
+#ifdef BUILD_UPNP
+       if(call->upnp_session != NULL) {
+               linphone_core_update_local_media_description_from_upnp(call->localdesc, call->upnp_session);
+       }
+#endif
        sal_call_set_local_media_description(call->op,call->localdesc);
        sal_call_accept(call->op);
        md=sal_call_get_final_media_description(call->op);
@@ -3124,8 +3140,14 @@ int linphone_core_pause_call(LinphoneCore *lc, LinphoneCall *call)
                return -1;
        }
        linphone_call_make_local_media_description(lc,call);
-       if (call->ice_session != NULL)
+       if (call->ice_session != NULL) {
                linphone_core_update_local_media_description_from_ice(call->localdesc, call->ice_session);
+       }
+#ifdef BUILD_UPNP
+       if(call->upnp_session != NULL) {
+               linphone_core_update_local_media_description_from_upnp(call->localdesc, call->upnp_session);
+       }
+#endif
        if (sal_media_description_has_dir(call->resultdesc,SalStreamSendRecv)){
                sal_media_description_set_dir(call->localdesc,SalStreamSendOnly);
                subject="Call on hold";
@@ -3203,8 +3225,14 @@ int linphone_core_resume_call(LinphoneCore *lc, LinphoneCall *the_call)
        if (call->audiostream) audio_stream_play(call->audiostream, NULL);
 
        linphone_call_make_local_media_description(lc,the_call);
-       if (call->ice_session != NULL)
+       if (call->ice_session != NULL) {
                linphone_core_update_local_media_description_from_ice(call->localdesc, call->ice_session);
+       }
+#ifdef BUILD_UPNP
+       if(call->upnp_session != NULL) {
+               linphone_core_update_local_media_description_from_upnp(call->localdesc, call->upnp_session);
+       }
+#endif
        sal_call_set_local_media_description(call->op,call->localdesc);
        sal_media_description_set_dir(call->localdesc,SalStreamSendRecv);
        if (call->params.in_conference && !call->current_params.in_conference) subject="Conference";
index d4abf16ca2eed60eff517aeeb750fe9242b56ce7..e110a03e3d9f2818ec0a4f11fbb4a6f5fabfde22 100644 (file)
@@ -206,7 +206,7 @@ int set_lock_file();
 int get_lock_file();
 int remove_lock_file();
 void check_sound_device(LinphoneCore *lc);
-void linphone_core_get_local_ip(LinphoneCore *lc, const char *to, char *result);
+void linphone_core_get_public_ip(LinphoneCore *lc, const char *to, char *result);
 bool_t host_has_ipv6_network();
 bool_t lp_spawn_command_line_sync(const char *command, char **result,int *command_ret);
 
@@ -593,6 +593,9 @@ int linphone_core_del_call( LinphoneCore *lc, LinphoneCall *call);
 int linphone_core_set_as_current_call(LinphoneCore *lc, LinphoneCall *call);
 int linphone_core_get_calls_nb(const LinphoneCore *lc);
 
+void linphone_core_add_iterate_hook(LinphoneCore *lc, LinphoneCoreIterateHook hook, void *hook_data);
+void linphone_core_remove_iterate_hook(LinphoneCore *lc, LinphoneCoreIterateHook hook, void *hook_data);
+
 void linphone_core_set_state(LinphoneCore *lc, LinphoneGlobalState gstate, const char *message);
 void linphone_call_make_local_media_description(LinphoneCore *lc, LinphoneCall *call);
 void linphone_core_update_streams(LinphoneCore *lc, LinphoneCall *call, SalMediaDescription *new_md);
index 3b47af42ce0ec2232811abfcbfc08f414fc32c13..ff046f92edb8a4fc1dffb08d1976f92c300d9730 100644 (file)
@@ -268,7 +268,7 @@ static char *guess_contact_for_register(LinphoneProxyConfig *obj){
                LCSipTransports tr;
                LinphoneAddress *contact;
                
-               linphone_core_get_local_ip(obj->lc,host,localip);
+               linphone_core_get_public_ip(obj->lc,host,localip);
                contact=linphone_address_new(obj->reg_identity);
                linphone_address_set_domain (contact,localip);
                linphone_address_set_port_int(contact,linphone_core_get_sip_port(obj->lc));
@@ -427,7 +427,7 @@ static dial_plan_t const dial_plans[]={
     {"Congo Democratic Republic"       ,"CD"           , "243"     , 9         , "00"  },
     {"Cook Islands"                 ,"CK"              , "682"     , 5         , "00"  },
     {"Costa Rica"                   ,"CR"              , "506"     , 8     , "00"      },
-    {"C\99te d'Ivoire"               ,"AD"               , "225"     , 8     , "00"  },
+    {"C�te d'Ivoire"             ,"AD"               , "225"     , 8     , "00"  },
     {"Croatia"                      ,"HR"              , "385"     , 9         , "00"  },
     {"Cuba"                         ,"CU"              , "53"      , 8     , "119" },
     {"Cyprus"                       ,"CY"              , "357"     , 8     , "00"      },
@@ -545,7 +545,7 @@ static dial_plan_t const dial_plans[]={
     {"Portugal"                     ,"PT"              , "351"     , 9     , "00"      },
     {"Puerto Rico"                  ,"PR"              , "1"       , 10        , "011" },
     {"Qatar"                        ,"QA"              , "974"     , 8     , "00"  },
-    {"R\8eunion Island"                          ,"RE"           , "262"     , 9     , "011"     },
+    {"R�union Island"                                ,"RE"           , "262"     , 9     , "011"     },
     {"Romania"                      ,"RO"              , "40"      , 9     , "00"      },
     {"Russian Federation"           ,"RU"              , "7"       , 10        , "8"   },
     {"Rwanda"                       ,"RW"              , "250"     , 9         , "00"  },
@@ -556,7 +556,7 @@ static dial_plan_t const dial_plans[]={
     {"Saint Vincent and the Grenadines","VC"   , "1"       , 10        , "011" },
     {"Samoa"                        ,"WS"              , "685"     , 7     , "0"       },
     {"San Marino"                   ,"SM"              , "378"     , 10        , "00"  },
-    {"S\8bo Tom\8e and Pr\92ncipe"        ,"ST"              , "239"     , 7         , "00"  },
+    {"S�o Tom� and Pr�ncipe"        ,"ST"                , "239"     , 7         , "00"  },
     {"Saudi Arabia"                 ,"SA"              , "966"     , 9         , "00"  },
     {"Senegal"                      ,"SN"              , "221"     , 9     , "00"  },
     {"Serbia"                       ,"RS"              , "381"     , 9     , "00"  },
index d0b71b3e41ae3523ae7a55716efd926e05f80b2c..0de861210cc53c237776cd620243bd64f65defa5 100644 (file)
@@ -21,14 +21,51 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 #include "private.h"
 
 #define UPNP_MAX_RETRY 4
+#define UPNP_SECTION_NAME "uPnP"
+
+/* Define private types */
+typedef struct _LpItem{
+       char *key;
+       char *value;
+} LpItem;
+
+typedef struct _LpSection{
+       char *name;
+       MSList *items;
+} LpSection;
+
+typedef struct _LpConfig{
+       FILE *file;
+       char *filename;
+       MSList *sections;
+       int modified;
+       int readonly;
+} LpConfig;
+
+/* Declare private functions */
+LpSection *lp_config_find_section(LpConfig *lpconfig, const char *name);
+void lp_section_remove_item(LpSection *sec, LpItem *item);
+void lp_config_set_string(LpConfig *lpconfig,const char *section, const char *key, const char *value);
+
+bool_t linphone_core_upnp_hook(void *data);
 
 UpnpPortBinding *upnp_port_binding_new();
-UpnpPortBinding * upnp_port_binding_retain(UpnpPortBinding *port);
+UpnpPortBinding *upnp_port_binding_copy(const UpnpPortBinding *port);
+bool_t upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2);
+UpnpPortBinding *upnp_port_binding_retain(UpnpPortBinding *port);
 void upnp_port_binding_release(UpnpPortBinding *port);
 
+MSList *upnp_config_list_port_bindings(struct _LpConfig *lpc);
+int upnp_config_add_port_binding(LinphoneCore *lc, const UpnpPortBinding *port);
+int upnp_config_remove_port_binding(LinphoneCore *lc, const UpnpPortBinding *port);
+
 int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *port);
 int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port);
 
+/**
+ * uPnP Callbacks
+ */
+
 /* Convert uPnP IGD logs to ortp logs */
 void linphone_upnp_igd_print(void *cookie, upnp_igd_print_level level, const char *fmt, va_list list) {
        int ortp_level = ORTP_DEBUG;
@@ -56,7 +93,6 @@ void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) {
        const char *ip_address = NULL;
        const char *connection_status = NULL;
        bool_t nat_enabled = FALSE;
-
        ms_mutex_lock(&lupnp->mutex);
 
        switch(event) {
@@ -68,13 +104,14 @@ void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) {
                nat_enabled = upnp_igd_get_nat_enabled(lupnp->upnp_igd_ctxt);
 
                if(ip_address == NULL || connection_status == NULL) {
-                       lupnp->state = UPNP_Pending;
+                       ms_message("uPnP IGD: Pending");
+                       lupnp->state = LinphoneUpnpStatePending;
                } else if(strcasecmp(connection_status, "Connected")  || !nat_enabled) {
-                       lupnp->state = UPNP_Ko;
+                       ms_message("uPnP IGD: Not Available");
+                       lupnp->state = LinphoneUpnpStateNotAvailable;
                } else {
-                       // Emit add port binding
-                       // Emit remove old port binding
-                       lupnp->state = UPNP_Ok;
+                       ms_message("uPnP IGD: Connected");
+                       lupnp->state = LinphoneUpnpStateOk;
                }
 
                break;
@@ -82,33 +119,56 @@ void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) {
        case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS:
                mapping = (upnp_igd_port_mapping *) arg;
                port_mapping = (UpnpPortBinding*) mapping->cookie;
-               port_mapping->remote_port = mapping->remote_port;
-               port_mapping->state = UPNP_Ok;
-               // TODO: SAVE IN CONFIG THE PORT
+               port_mapping->external_port = mapping->remote_port;
+               port_mapping->state = LinphoneUpnpStateOk;
+               ms_message("uPnP IGD: Added port binding %s|%d->%s:%d",
+                                                       (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
+                                                       port_mapping->external_port,
+                                                       port_mapping->local_addr,
+                                                       port_mapping->local_port);
+               upnp_config_add_port_binding(lc, port_mapping);
+
                upnp_port_binding_release(port_mapping);
                break;
 
        case UPNP_IGD_PORT_MAPPING_ADD_FAILURE:
                mapping = (upnp_igd_port_mapping *) arg;
                port_mapping = (UpnpPortBinding*) mapping->cookie;
-               upnp_context_send_add_port_binding(lc, port_mapping);
+               if(upnp_context_send_add_port_binding(lc, port_mapping) != 0) {
+                       ms_error("uPnP IGD: Can't add port binding %s|%d->%s:%d",
+                                                               (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
+                                                               port_mapping->external_port,
+                                                               port_mapping->local_addr,
+                                                               port_mapping->local_port);
+               }
+
                upnp_port_binding_release(port_mapping);
                break;
 
        case UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS:
                mapping = (upnp_igd_port_mapping *) arg;
                port_mapping = (UpnpPortBinding*) mapping->cookie;
-               port_mapping->remote_port = -1;
-               port_mapping->state = UPNP_Idle;
-               // TODO: REMOVE FROM CONFIG THE PORT
+               port_mapping->state = LinphoneUpnpStateIdle;
+               ms_message("uPnP IGD: Removed port binding %s|%d->%d",
+                                                       (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
+                                                       port_mapping->external_port,
+                                                       port_mapping->local_port);
+               upnp_config_remove_port_binding(lc, port_mapping);
+
                upnp_port_binding_release(port_mapping);
                break;
 
        case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE:
                mapping = (upnp_igd_port_mapping *) arg;
                port_mapping = (UpnpPortBinding*) mapping->cookie;
-               upnp_context_send_remove_port_binding(lc, port_mapping);
-               // TODO: REMOVE FROM CONFIG THE PORT (DON'T TRY ANYMORE)
+               if(upnp_context_send_remove_port_binding(lc, port_mapping) != 0) {
+                       ms_error("uPnP IGD: Can't remove port binding %s|%d->%d",
+                                                               (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
+                                                               port_mapping->external_port,
+                                                               port_mapping->local_port);
+                       upnp_config_remove_port_binding(lc, port_mapping);
+               }
+
                upnp_port_binding_release(port_mapping);
                break;
 
@@ -119,36 +179,123 @@ void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) {
        ms_mutex_unlock(&lupnp->mutex);
 }
 
+
+/**
+ * uPnP Context
+ */
+
+int upnp_context_init(LinphoneCore *lc) {
+       LCSipTransports transport;
+       UpnpContext *lupnp = &lc->upnp;
+       const char *ip_address;
+
+       ms_mutex_init(&lupnp->mutex, NULL);
+       lupnp->pending_configs = NULL;
+       lupnp->state = LinphoneUpnpStateIdle;
+       lupnp->old_state = LinphoneUpnpStateIdle;
+       ms_message("uPnP IGD: Init");
+
+       linphone_core_get_sip_transports(lc, &transport);
+       if(transport.udp_port != 0) {
+               lupnp->sip_udp = upnp_port_binding_new();
+               lupnp->sip_udp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
+               lupnp->sip_udp->local_port = transport.udp_port;
+       } else {
+               lupnp->sip_udp = NULL;
+       }
+       if(transport.tcp_port != 0) {
+               lupnp->sip_tcp = upnp_port_binding_new();
+               lupnp->sip_tcp->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
+               lupnp->sip_tcp->local_port = transport.tcp_port;
+       } else {
+               lupnp->sip_tcp = NULL;
+       }
+       if(transport.tls_port != 0) {
+               lupnp->sip_tls = upnp_port_binding_new();
+               lupnp->sip_tls->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
+               lupnp->sip_tls->local_port = transport.tls_port;
+       } else {
+               lupnp->sip_tls = NULL;
+       }
+
+       linphone_core_add_iterate_hook(lc, linphone_core_upnp_hook, lc);
+
+       lupnp->upnp_igd_ctxt = NULL;
+       lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, lc);
+       if(lupnp->upnp_igd_ctxt == NULL) {
+               lupnp->state = LinphoneUpnpStateKo;
+               ms_error("Can't create uPnP IGD context");
+               return -1;
+       }
+
+       ip_address = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
+       if(lupnp->sip_udp != NULL) {
+               strncpy(lupnp->sip_udp->local_addr, ip_address, sizeof(lupnp->sip_udp->local_addr));
+       }
+       if(lupnp->sip_tcp != NULL) {
+               strncpy(lupnp->sip_tcp->local_addr, ip_address, sizeof(lupnp->sip_tcp->local_addr));
+       }
+       if(lupnp->sip_tls != NULL) {
+               strncpy(lupnp->sip_tls->local_addr, ip_address, sizeof(lupnp->sip_tls->local_addr));
+       }
+
+       lupnp->state = LinphoneUpnpStatePending;
+       return 0;
+}
+
+void upnp_context_uninit(LinphoneCore *lc) {
+       UpnpContext *lupnp = &lc->upnp;
+       linphone_core_remove_iterate_hook(lc, linphone_core_upnp_hook, lc);
+
+       if(lupnp->sip_udp != NULL) {
+               upnp_port_binding_release(lupnp->sip_udp);
+       }
+       if(lupnp->sip_tcp != NULL) {
+               upnp_port_binding_release(lupnp->sip_tcp);
+       }
+       if(lupnp->sip_tls != NULL) {
+               upnp_port_binding_release(lupnp->sip_tls);
+       }
+       if(lupnp->upnp_igd_ctxt != NULL) {
+               upnp_igd_destroy(lupnp->upnp_igd_ctxt);
+       }
+       ms_mutex_destroy(&lupnp->mutex);
+
+       ms_message("uPnP IGD: Uninit");
+}
+
 int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port) {
        UpnpContext *lupnp = &lc->upnp;
        upnp_igd_port_mapping mapping;
-       char * local_host = NULL;
        int ret;
-       if(port->state == UPNP_Idle) {
-               port->remote_port = -1;
+       if(port->state == LinphoneUpnpStateIdle) {
+               port->external_port = -1;
                port->retry = 0;
-               port->state = UPNP_Pending;
+               port->state = LinphoneUpnpStateAdding;
+       } else if(port->state != LinphoneUpnpStateAdding) {
+               ms_error("uPnP: try to add a port binding in wrong state: %d", port->state);
+               return -2;
        }
+
        if(port->retry >= UPNP_MAX_RETRY) {
                ret = -1;
        } else {
-               local_host = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
                mapping.cookie = upnp_port_binding_retain(port);
                mapping.local_port = port->local_port;
-               mapping.local_host = local_host;
-               mapping.remote_port = rand()%1024 + 1024;
+               mapping.local_host = port->local_addr;
+               if(port->external_port == -1)
+                       mapping.remote_port = rand()%1024 + 1024; // TODO: use better method
+               else
+                       mapping.remote_port = port->external_port;
                mapping.remote_host = "";
                mapping.description = PACKAGE_NAME;
                mapping.protocol = port->protocol;
 
                port->retry++;
                ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
-               if(local_host != NULL) {
-                       free(local_host);
-               }
        }
        if(ret != 0) {
-               port->state = UPNP_Ko;
+               port->state = LinphoneUpnpStateKo;
        }
        return ret;
 }
@@ -157,76 +304,163 @@ int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *por
        UpnpContext *lupnp = &lc->upnp;
        upnp_igd_port_mapping mapping;
        int ret;
-       if(port->state == UPNP_Idle) {
+       if(port->state == LinphoneUpnpStateIdle) {
                port->retry = 0;
-               port->state = UPNP_Pending;
+               port->state = LinphoneUpnpStateRemoving;
+       } else if(port->state != LinphoneUpnpStateRemoving) {
+               ms_error("uPnP: try to remove a port binding in wrong state: %d", port->state);
+               return -2;
        }
+
        if(port->retry >= UPNP_MAX_RETRY) {
                ret = -1;
        } else {
                mapping.cookie = upnp_port_binding_retain(port);
-               mapping.remote_port = port->remote_port;
+               mapping.remote_port = port->external_port;
                mapping.remote_host = "";
                mapping.protocol = port->protocol;
                port->retry++;
                ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
        }
        if(ret != 0) {
-               port->state = UPNP_Ko;
+               port->state = LinphoneUpnpStateKo;
        }
        return ret;
 }
 
+/*
+ * uPnP Core interfaces
+ */
+
 int upnp_call_process(LinphoneCall *call) {
        LinphoneCore *lc = call->core;
        UpnpContext *lupnp = &lc->upnp;
        int ret = -1;
+       UpnpState oldState;
+       const char *local_addr, *external_addr;
 
        ms_mutex_lock(&lupnp->mutex);
        // Don't handle when the call
-       if(lupnp->state != UPNP_Ko && call->upnp_session != NULL) {
+       if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
                ret = 0;
+               local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
+               external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
 
                /*
                 * Audio part
                 */
-               call->upnp_session->audio_rtp->local_port = call->audio_port;
-               call->upnp_session->audio_rtcp->local_port = call->audio_port+1;
-               if(call->upnp_session->audio_rtp->state == UPNP_Idle && call->audiostream != NULL) {
+               strncpy(call->upnp_session->audio->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
+               strncpy(call->upnp_session->audio->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
+               call->upnp_session->audio->rtp->local_port = call->audio_port;
+               strncpy(call->upnp_session->audio->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
+               strncpy(call->upnp_session->audio->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
+               call->upnp_session->audio->rtcp->local_port = call->audio_port+1;
+               if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle && call->audiostream != NULL) {
                        // Add audio port binding
-                       upnp_context_send_add_port_binding(lc, call->upnp_session->audio_rtp);
-               } else if(call->upnp_session->audio_rtp->state == UPNP_Ok && call->audiostream == NULL) {
+                       upnp_context_send_add_port_binding(lc, call->upnp_session->audio->rtp);
+               } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk && call->audiostream == NULL) {
                        // Remove audio port binding
-                       upnp_context_send_remove_port_binding(lc, call->upnp_session->audio_rtp);
+                       upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtp);
                }
-               if(call->upnp_session->audio_rtcp->state == UPNP_Idle && call->audiostream != NULL) {
+               if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle && call->audiostream != NULL) {
                        // Add audio port binding
-                       upnp_context_send_add_port_binding(lc, call->upnp_session->audio_rtcp);
-               } else if(call->upnp_session->audio_rtcp->state == UPNP_Ok && call->audiostream == NULL) {
+                       upnp_context_send_add_port_binding(lc, call->upnp_session->audio->rtcp);
+               } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk && call->audiostream == NULL) {
                        // Remove audio port binding
-                       upnp_context_send_remove_port_binding(lc, call->upnp_session->audio_rtcp);
+                       upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtcp);
+               }
+               if((call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle) &&
+                               (call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle)) {
+                       call->upnp_session->audio->state = LinphoneUpnpStateOk;
+               } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateAdding ||
+                               call->upnp_session->audio->rtp->state == LinphoneUpnpStateRemoving ||
+                               call->upnp_session->audio->rtcp->state == LinphoneUpnpStateAdding ||
+                               call->upnp_session->audio->rtcp->state == LinphoneUpnpStateRemoving) {
+                       call->upnp_session->audio->state = LinphoneUpnpStatePending;
+               } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateKo ||
+                               call->upnp_session->audio->rtp->state == LinphoneUpnpStateKo) {
+                       call->upnp_session->audio->state = LinphoneUpnpStateKo;
+               } else {
+                       call->upnp_session->audio->state = LinphoneUpnpStateIdle;
                }
 
                /*
                 * Video part
                 */
-               call->upnp_session->video_rtp->local_port = call->video_port;
-               call->upnp_session->video_rtcp->local_port = call->video_port+1;
-               if(call->upnp_session->video_rtp->state == UPNP_Idle && call->videostream != NULL) {
+               strncpy(call->upnp_session->video->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
+               strncpy(call->upnp_session->video->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
+               call->upnp_session->video->rtp->local_port = call->video_port;
+               strncpy(call->upnp_session->video->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
+               strncpy(call->upnp_session->video->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
+               call->upnp_session->video->rtcp->local_port = call->video_port+1;
+               if(call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle && call->videostream != NULL) {
                        // Add video port binding
-                       upnp_context_send_add_port_binding(lc, call->upnp_session->video_rtp);
-               } else if(call->upnp_session->video_rtp->state == UPNP_Ok && call->videostream == NULL) {
+                       upnp_context_send_add_port_binding(lc, call->upnp_session->video->rtp);
+               } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateOk && call->videostream == NULL) {
                        // Remove video port binding
-                       upnp_context_send_remove_port_binding(lc, call->upnp_session->video_rtp);
+                       upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtp);
                }
-               if(call->upnp_session->video_rtcp->state == UPNP_Idle && call->videostream != NULL) {
+               if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle && call->videostream != NULL) {
                        // Add video port binding
-                       upnp_context_send_add_port_binding(lc, call->upnp_session->video_rtcp);
-               } else if(call->upnp_session->video_rtcp->state == UPNP_Ok && call->videostream == NULL) {
+                       upnp_context_send_add_port_binding(lc, call->upnp_session->video->rtcp);
+               } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk && call->videostream == NULL) {
                        // Remove video port binding
-                       upnp_context_send_remove_port_binding(lc, call->upnp_session->video_rtcp);
+                       upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtcp);
+               }
+               if((call->upnp_session->video->rtp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle) &&
+                               (call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle)) {
+                       call->upnp_session->video->state = LinphoneUpnpStateOk;
+               } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateAdding ||
+                               call->upnp_session->video->rtp->state == LinphoneUpnpStateRemoving ||
+                               call->upnp_session->video->rtcp->state == LinphoneUpnpStateAdding ||
+                               call->upnp_session->video->rtcp->state == LinphoneUpnpStateRemoving) {
+                       call->upnp_session->video->state = LinphoneUpnpStatePending;
+               } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateKo ||
+                               call->upnp_session->video->rtp->state == LinphoneUpnpStateKo) {
+                       call->upnp_session->video->state = LinphoneUpnpStateKo;
+               } else {
+                       call->upnp_session->video->state = LinphoneUpnpStateIdle;
+               }
+
+               /*
+                * Update session state
+                */
+               oldState = call->upnp_session->state;
+               if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
+                       call->upnp_session->video->state == LinphoneUpnpStateOk) {
+                       call->upnp_session->state = LinphoneUpnpStateOk;
+               } else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
+                               call->upnp_session->video->state == LinphoneUpnpStatePending) {
+                       call->upnp_session->state = LinphoneUpnpStatePending;
+               } else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
+                               call->upnp_session->video->state == LinphoneUpnpStateKo) {
+                       call->upnp_session->state = LinphoneUpnpStateKo;
+               } else {
+                       call->upnp_session->state = LinphoneUpnpStateIdle;
+               }
+
+               /* When change is done proceed update */
+               if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
+                               (call->upnp_session->state == LinphoneUpnpStateOk || call->upnp_session->state == LinphoneUpnpStateKo)) {
+                       switch (call->state) {
+                               case LinphoneCallUpdating:
+                                       linphone_core_start_update_call(call->core, call);
+                                       break;
+                               case LinphoneCallUpdatedByRemote:
+                                       linphone_core_start_accept_call_update(call->core, call);
+                                       break;
+                               case LinphoneCallOutgoingInit:
+                                       linphone_core_proceed_with_invite_if_ready(call->core, call, NULL);
+                                       break;
+                               case LinphoneCallIdle:
+                                       linphone_core_notify_incoming_call(call->core, call);
+                                       break;
+                               default:
+                                       break;
+                       }
                }
        }
+
        ms_mutex_unlock(&lupnp->mutex);
        return ret;
 }
@@ -235,71 +469,125 @@ int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
        return upnp_call_process(call);
 }
 
-int upnp_context_init(LinphoneCore *lc) {
-       LCSipTransports transport;
+bool_t linphone_core_upnp_hook(void *data) {
+       char key[64];
+       MSList *port_bindings = NULL;
+       MSList *port_bindings_item;
+       UpnpPortBinding *port_mapping;
+       LinphoneCore *lc = (LinphoneCore *)data;
        UpnpContext *lupnp = &lc->upnp;
-       ms_mutex_init(&lupnp->mutex, NULL);
-       lupnp->state = UPNP_Idle;
+       ms_mutex_lock(&lupnp->mutex);
 
-       linphone_core_get_sip_transports(lc, &transport);
-       if(transport.udp_port != 0) {
-               lupnp->sip_udp = upnp_port_binding_new();
-               lupnp->sip_udp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
-       } else {
-               lupnp->sip_udp = NULL;
-       }
-       if(transport.tcp_port != 0) {
-               lupnp->sip_tcp = upnp_port_binding_new();
-               lupnp->sip_tcp->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
-       } else {
-               lupnp->sip_tcp = NULL;
+       if(lupnp->state == LinphoneUpnpStateOk && lupnp->old_state != LinphoneUpnpStateOk) {
+               // Remove old mapping
+               port_bindings = upnp_config_list_port_bindings(lc->config);
+               if(port_bindings != NULL) {
+                       for(port_bindings_item = port_bindings;port_bindings_item!=NULL;port_bindings_item=port_bindings_item->next) {
+                               port_mapping = (UpnpPortBinding *)port_bindings_item->data;
+                               upnp_context_send_remove_port_binding(lc, port_mapping);
+                       }
+                       ms_list_for_each(port_bindings,(void (*)(void*))upnp_port_binding_release);
+                       port_bindings = ms_list_free(port_bindings);
+               }
        }
-       if(transport.tls_port != 0) {
-               lupnp->sip_tls = upnp_port_binding_new();
-               lupnp->sip_tls->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
-       } else {
-               lupnp->sip_tls = NULL;
+
+       if(lupnp->state == LinphoneUpnpStateOk && lupnp->old_state != LinphoneUpnpStateOk) {
+               // Add port bindings
+               if(lupnp->sip_udp != NULL) {
+                       upnp_context_send_add_port_binding(lc, lupnp->sip_udp);
+               }
+               if(lupnp->sip_tcp != NULL) {
+                       upnp_context_send_add_port_binding(lc, lupnp->sip_tcp);
+               }
+               if(lupnp->sip_tls != NULL) {
+                       upnp_context_send_add_port_binding(lc, lupnp->sip_tls);
+               }
        }
-       lupnp->upnp_igd_ctxt = NULL;
-       lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, lc);
-       if(lupnp->upnp_igd_ctxt == NULL ) {
-               lupnp->state = UPNP_Ko;
-               ms_error("Can't create uPnP IGD context");
-               return -1;
+
+       /* Update configs */
+       for(port_bindings_item = lupnp->pending_configs;port_bindings_item!=NULL;port_bindings_item=port_bindings_item->next) {
+               port_mapping = (UpnpPortBinding *)port_bindings_item->data;
+               if(port_mapping->state == LinphoneUpnpStateAdding) {
+                       snprintf(key, sizeof(key), "%s-%d-%d",
+                                               (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
+                                                               port_mapping->external_port,
+                                                               port_mapping->local_port);
+                       lp_config_set_string(lc->config, UPNP_SECTION_NAME, key, "");
+               }
+               if(port_mapping->state == LinphoneUpnpStateRemoving) {
+                       snprintf(key, sizeof(key), "%s-%d-%d",
+                                               (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
+                                                               port_mapping->external_port,
+                                                               port_mapping->local_port);
+                       lp_config_set_string(lc->config, UPNP_SECTION_NAME, key, NULL);
+               }
        }
-       lupnp->state = UPNP_Pending;
-       return 0;
+       ms_list_for_each(lupnp->pending_configs,(void (*)(void*))upnp_port_binding_release);
+       lupnp->pending_configs = ms_list_free(lupnp->pending_configs);
+
+       lupnp->old_state = lupnp->state;
+       ms_mutex_unlock(&lupnp->mutex);
+       return TRUE;
 }
 
-void upnp_context_uninit(LinphoneCore *lc) {
-       // Emit remove port (sip & saved)
-       UpnpContext *lupnp = &lc->upnp;
-       if(lupnp->sip_udp != NULL) {
-               upnp_port_binding_release(lupnp->sip_udp);
-       }
-       if(lupnp->sip_tcp != NULL) {
-               upnp_port_binding_release(lupnp->sip_tcp);
-       }
-       if(lupnp->sip_tls != NULL) {
-               upnp_port_binding_release(lupnp->sip_tls);
-       }
-       if(lupnp->upnp_igd_ctxt != NULL) {
-               upnp_igd_destroy(lupnp->upnp_igd_ctxt);
+void linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
+       int i;
+       SalStreamDescription *stream;
+       UpnpStream *upnpStream;
+
+       for (i = 0; i < desc->nstreams; i++) {
+               stream = &desc->streams[i];
+               upnpStream = NULL;
+               if(stream->type == SalAudio) {
+                       upnpStream = session->audio;
+               } else if(stream->type == SalVideo) {
+                       upnpStream = session->video;
+               }
+               if(upnpStream != NULL) {
+                       if(upnpStream->rtp->state == LinphoneUpnpStateOk) {
+                               strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
+                               stream->rtp_port = upnpStream->rtp->external_port;
+                       }
+                       if(upnpStream->rtcp->state == LinphoneUpnpStateOk) {
+                               strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
+                               stream->rtcp_port = upnpStream->rtcp->external_port;
+                       }
+               }
        }
-       ms_mutex_destroy(&lupnp->mutex);
 }
 
+
+/*
+ * uPnP Port Binding
+ */
+
 UpnpPortBinding *upnp_port_binding_new() {
        UpnpPortBinding *port = NULL;
        port = ms_new0(UpnpPortBinding,1);
        ms_mutex_init(&port->mutex, NULL);
-       port->state = UPNP_Idle;
+       port->state = LinphoneUpnpStateIdle;
+       port->local_addr[0] = '\0';
        port->local_port = -1;
-       port->remote_port = -1;
+       port->external_addr[0] = '\0';
+       port->external_port = -1;
        port->ref = 1;
        return port;
 }
 
+UpnpPortBinding *upnp_port_binding_copy(const UpnpPortBinding *port) {
+       UpnpPortBinding *new_port = NULL;
+       new_port = ms_new0(UpnpPortBinding,1);
+       memcpy(new_port, port, sizeof(UpnpPortBinding));
+       ms_mutex_init(&new_port->mutex, NULL);
+       new_port->ref = 1;
+       return new_port;
+}
+
+bool_t upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
+       return port1->protocol == port2->protocol && port1->local_port == port2->local_port &&
+                       port1->external_port && port2->external_port;
+}
+
 UpnpPortBinding *upnp_port_binding_retain(UpnpPortBinding *port) {
        ms_mutex_lock(&port->mutex);
        port->ref++;
@@ -318,25 +606,177 @@ void upnp_port_binding_release(UpnpPortBinding *port) {
        ms_mutex_unlock(&port->mutex);
 }
 
+/*
+ * uPnP Stream
+ */
+
+UpnpStream* upnp_stream_new() {
+       UpnpStream *stream = ms_new0(UpnpStream,1);
+       stream->state = LinphoneUpnpStateIdle;
+       stream->rtp = upnp_port_binding_new();
+       stream->rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
+       stream->rtcp = upnp_port_binding_new();
+       stream->rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
+       return NULL;
+}
+
+void upnp_stream_destroy(UpnpStream* stream) {
+       upnp_port_binding_release(stream->rtp);
+       upnp_port_binding_release(stream->rtcp);
+       ms_free(stream);
+}
+
+
+/*
+ * uPnP Session
+ */
+
 UpnpSession* upnp_session_new() {
        UpnpSession *session = ms_new0(UpnpSession,1);
-       session->state = UPNP_Idle;
-       session->audio_rtp = upnp_port_binding_new();
-       session->audio_rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
-       session->audio_rtcp = upnp_port_binding_new();
-       session->audio_rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
-       session->video_rtp = upnp_port_binding_new();
-       session->video_rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
-       session->video_rtcp = upnp_port_binding_new();
-       session->video_rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
+       session->state = LinphoneUpnpStateIdle;
+       session->audio = upnp_stream_new();
+       session->video = upnp_stream_new();
        return NULL;
 }
 
-void upnp_session_destroy(UpnpSession* session) {
-       upnp_port_binding_release(session->audio_rtp);
-       upnp_port_binding_release(session->audio_rtcp);
-       upnp_port_binding_release(session->video_rtp);
-       upnp_port_binding_release(session->video_rtp);
-       // TODO: send remove
-       ms_free(session);
+void upnp_session_destroy(LinphoneCall* call) {
+       LinphoneCore *lc = call->core;
+
+       /* Remove bindings */
+       if(call->upnp_session->audio->rtp->state != LinphoneUpnpStateKo && call->upnp_session->audio->rtp->state != LinphoneUpnpStateIdle) {
+               upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtp);
+       }
+       if(call->upnp_session->audio->rtcp->state != LinphoneUpnpStateKo && call->upnp_session->audio->rtcp->state != LinphoneUpnpStateIdle) {
+               upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtcp);
+       }
+       if(call->upnp_session->video->rtp->state != LinphoneUpnpStateKo && call->upnp_session->video->rtp->state != LinphoneUpnpStateIdle) {
+               upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtp);
+       }
+       if(call->upnp_session->video->rtcp->state != LinphoneUpnpStateKo && call->upnp_session->video->rtcp->state != LinphoneUpnpStateIdle) {
+               upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtcp);
+       }
+
+       upnp_stream_destroy(call->upnp_session->audio);
+       upnp_stream_destroy(call->upnp_session->video);
+       ms_free(call->upnp_session);
+       call->upnp_session = NULL;
+}
+
+
+/*
+ * uPnP Config
+ */
+
+MSList *upnp_config_list_port_bindings(struct _LpConfig *lpc) {
+       char protocol_str[4]; // TCP or UDP
+       upnp_igd_ip_protocol protocol;
+       int external_port;
+       int local_port;
+       MSList *retList = NULL;
+       UpnpPortBinding *port;
+       bool_t valid;
+       MSList *elem;
+       MSList *prev_elem;
+       LpItem *item;
+       LpSection *sec=lp_config_find_section(lpc, UPNP_SECTION_NAME);
+       if(sec == NULL)
+               return retList;
+
+       elem = sec->items;
+       while(elem != NULL) {
+               item=(LpItem*)elem->data;
+               valid = TRUE;
+               if(sscanf(item->key, "%3s-%i-%i", protocol_str, &external_port, &local_port) == 3) {
+                       if(strcasecmp(protocol_str, "TCP") == 0) {
+                               protocol = UPNP_IGD_IP_PROTOCOL_TCP;
+                       } else if(strcasecmp(protocol_str, "UDP") == 0) {
+                               protocol = UPNP_IGD_IP_PROTOCOL_UDP;
+                       } else {
+                               valid = FALSE;
+                       }
+                       if(valid) {
+                               port = upnp_port_binding_new();
+                               port->protocol = protocol;
+                               port->external_port = external_port;
+                               port->local_port = local_port;
+                               retList = ms_list_append(retList, port);
+                       }
+               } else {
+                       valid = FALSE;
+               }
+               prev_elem = elem;
+               elem = ms_list_next(elem);
+               if(!valid) {
+                       ms_warning("uPnP configuration invalid line: %s", item->key);
+                       lp_section_remove_item(sec, item);
+               }
+       }
+
+       return retList;
+}
+
+int upnp_config_add_port_binding(LinphoneCore *lc, const UpnpPortBinding *port) {
+       UpnpContext *lupnp = &lc->upnp;
+       MSList *list = lupnp->pending_configs;
+       UpnpPortBinding *list_port;
+       bool_t remove;
+       bool_t add = TRUE;
+       while(list != NULL) {
+               remove = FALSE;
+               list_port = (UpnpPortBinding *)list->data;
+               if(upnp_port_binding_equal(list_port, port) == TRUE) {
+                       if(list_port->state == LinphoneUpnpStateAdding) {
+                               add = FALSE;
+                               break;
+                       }
+                       if(list_port->state == LinphoneUpnpStateRemoving) {
+                               remove = TRUE;
+                               break;
+                       }
+               }
+               list = ms_list_next(list);
+       }
+
+       if(remove) {
+               lupnp->pending_configs = ms_list_remove(list, list_port);
+       } else if(add) {
+               list_port = upnp_port_binding_copy(port);
+               list_port->state = LinphoneUpnpStateAdding;
+               lupnp->pending_configs = ms_list_append(list, list_port);
+       }
+
+       return 0;
+}
+
+int upnp_config_remove_port_binding(LinphoneCore *lc, const UpnpPortBinding *port) {
+       UpnpContext *lupnp = &lc->upnp;
+       MSList *list = lupnp->pending_configs;
+       UpnpPortBinding *list_port;
+       bool_t remove;
+       bool_t add = TRUE;
+       while(list != NULL) {
+               remove = FALSE;
+               list_port = (UpnpPortBinding *)list->data;
+               if(upnp_port_binding_equal(list_port, port)) {
+                       if(list_port->state == LinphoneUpnpStateRemoving) {
+                               add = FALSE;
+                               break;
+                       }
+                       if(list_port->state == LinphoneUpnpStateAdding) {
+                               remove = TRUE;
+                               break;
+                       }
+               }
+               list = ms_list_next(list);
+       }
+
+       if(remove) {
+               lupnp->pending_configs = ms_list_remove(list, list_port);
+       } else if(add) {
+               list_port = upnp_port_binding_copy(port);
+               list_port->state = LinphoneUpnpStateRemoving;
+               lupnp->pending_configs = ms_list_append(list, list_port);
+       }
+
+       return 0;
 }
index f0a93d1d6e1ef1717e252406bc9563796ccfa7fd..492b35234b26ca88ad0691c82ee909d6a4a5f87b 100644 (file)
@@ -22,33 +22,42 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
 #include "mediastreamer2/upnp_igd.h"
 #include "linphonecore.h"
+#include "sal.h"
 
 typedef enum {
-       UPNP_Idle,
-       UPNP_Pending,
-       UPNP_Ok,
-       UPNP_Ko,
+       LinphoneUpnpStateIdle,
+       LinphoneUpnpStatePending,
+       LinphoneUpnpStateAdding,   // Only used by port binding
+       LinphoneUpnpStateRemoving, // Only used by port binding
+       LinphoneUpnpStateNotAvailable,  // Only used by uPnP context
+       LinphoneUpnpStateOk,
+       LinphoneUpnpStateKo,
 } UpnpState;
 
-typedef struct _UpnpSession  UpnpSession;
 
 typedef struct _UpnpPortBinding {
        ms_mutex_t mutex;
        UpnpState state;
        upnp_igd_ip_protocol protocol;
+       char local_addr[LINPHONE_IPADDR_SIZE];
        int local_port;
-       int remote_port;
+       char external_addr[LINPHONE_IPADDR_SIZE];
+       int external_port;
        int retry;
        int ref;
 } UpnpPortBinding;
 
-struct _UpnpSession {
-       UpnpPortBinding *audio_rtp;
-       UpnpPortBinding *audio_rtcp;
-       UpnpPortBinding *video_rtp;
-       UpnpPortBinding *video_rtcp;
+typedef struct _UpnpStream {
+       UpnpPortBinding *rtp;
+       UpnpPortBinding *rtcp;
        UpnpState state;
-};
+} UpnpStream;
+
+typedef struct _UpnpSession {
+       UpnpStream *audio;
+       UpnpStream *video;
+       UpnpState state;
+} UpnpSession;
 
 typedef struct _UpnpContext {
        upnp_igd_context *upnp_igd_ctxt;
@@ -56,15 +65,17 @@ typedef struct _UpnpContext {
        UpnpPortBinding *sip_tls;
        UpnpPortBinding *sip_udp;
        UpnpState state;
-       MSList *pending_bindinds;
+       UpnpState old_state;
+       MSList *pending_configs;
+
        ms_mutex_t mutex;
 } UpnpContext;
 
-
+void linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session);
 int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call);
 int upnp_call_process(LinphoneCall *call);
 UpnpSession* upnp_session_new();
-void upnp_session_destroy(UpnpSession* session);
+void upnp_session_destroy(LinphoneCall* call);
 
 int upnp_context_init(LinphoneCore *lc);
 void upnp_context_uninit(LinphoneCore *lc);
index 39998cb245606b904a77e093db168057f87bf8b0..34de96d6b33f58b248f7d46e9c25edb11e6a5426 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 39998cb245606b904a77e093db168057f87bf8b0
+Subproject commit 34de96d6b33f58b248f7d46e9c25edb11e6a5426