diff --git a/configure.ac b/configure.ac index 10370968c..3526920fc 100644 --- a/configure.ac +++ b/configure.ac @@ -161,6 +161,32 @@ AC_ARG_ENABLE(tools, *) AC_MSG_ERROR(bad value ${enableval} for --enable-tools) ;; esac],[build_tools=check]) +dnl check for installed version of libupnp +AC_ARG_ENABLE(upnp, + [AS_HELP_STRING([--disable-upnp], [Disable uPnP support])], + [case "${enableval}" in + yes) build_upnp=true ;; + no) build_upnp=false ;; + *) AC_MSG_ERROR(bad value ${enableval} for --disable-upnp) ;; + esac],[build_upnp=auto]) + +if test "$build_upnp" != "false" ; then +PKG_CHECK_MODULES([LIBUPNP], [libupnp], [build_upnp=true], + [ + if test "$build_upnp" == "true" ; then + AC_MSG_ERROR([libupnp not found.]) + else + build_upnp=false + fi + ]) + +fi + +AM_CONDITIONAL(BUILD_UPNP, test x$build_upnp != xfalse) +if test "$build_upnp" != "false" ; then + AC_DEFINE(BUILD_UPNP, 1, [Define if upnp enabled]) +fi + dnl check libxml2 (needed for tools) if test "$build_tools" != "false" ; then PKG_CHECK_MODULES(LIBXML2, [libxml-2.0],[], diff --git a/console/commands.c b/console/commands.c index 472531795..3ec2ed65b 100644 --- a/console/commands.c +++ b/console/commands.c @@ -206,7 +206,7 @@ static LPC_COMMAND commands[] = { { "autoanswer", lpc_cmd_autoanswer, "Show/set auto-answer mode", "'autoanswer' \t: show current autoanswer mode\n" "'autoanswer enable'\t: enable autoanswer mode\n" - "'autoanswer disable'\t: disable autoanswer mode \n"}, + "'autoanswer disable'\t: disable autoanswer mode��\n"}, { "proxy", lpc_cmd_proxy, "Manage proxies", "'proxy list' : list all proxy setups.\n" "'proxy add' : add a new proxy setup.\n" @@ -896,6 +896,9 @@ lpc_cmd_firewall(LinphoneCore *lc, char *args) case LinphonePolicyUseIce: linphonec_out("Using ice with stun server %s to discover firewall address\n", setting ? setting : linphone_core_get_stun_server(lc)); break; + case LinphonePolicyUseUpnp: + linphonec_out("Using uPnP IGD protocol\n"); + break; } return 1; } diff --git a/coreapi/Makefile.am b/coreapi/Makefile.am index 790612cab..eab65b147 100644 --- a/coreapi/Makefile.am +++ b/coreapi/Makefile.am @@ -47,15 +47,21 @@ liblinphone_la_SOURCES=\ lsd.c linphonecore_utils.h \ ec-calibrator.c \ conference.c \ - linphone_tunnel.cc \ $(GITVERSION_FILE) + +if BUILD_UPNP +liblinphone_la_SOURCES+=upnp.c upnp.h +endif if BUILD_WIZARD liblinphone_la_SOURCES+=sipwizard.c endif +liblinphone_la_SOURCES+=linphone_tunnel_config.c if BUILD_TUNNEL -liblinphone_la_SOURCES+=TunnelManager.cc TunnelManager.hh +liblinphone_la_SOURCES+=linphone_tunnel.cc TunnelManager.cc TunnelManager.hh +else +liblinphone_la_SOURCES+=linphone_tunnel_stubs.c endif diff --git a/coreapi/callbacks.c b/coreapi/callbacks.c index 784c1f1bd..948fc720b 100644 --- a/coreapi/callbacks.c +++ b/coreapi/callbacks.c @@ -261,6 +261,13 @@ static void call_received(SalOp *h){ ms_message("Defer ringing to gather ICE candidates"); return; } +#ifdef BUILD_UPNP + if ((linphone_core_get_firewall_policy(lc) == LinphonePolicyUseUpnp) && (call->upnp_session != NULL)) { + /* Defer ringing until the end of the ICE candidates gathering process. */ + ms_message("Defer ringing to gather uPnP candidates"); + return; + } +#endif //BUILD_UPNP linphone_core_notify_incoming_call(lc,call); } @@ -334,6 +341,11 @@ static void call_accepted(SalOp *op){ if (call->ice_session != NULL) { linphone_core_update_ice_from_remote_media_description(call, sal_call_get_remote_media_description(op)); } +#ifdef BUILD_UPNP + if (call->upnp_session != NULL) { + linphone_core_update_upnp_from_remote_media_description(call, sal_call_get_remote_media_description(op)); + } +#endif //BUILD_UPNP md=sal_call_get_final_media_description(op); call->params.has_video &= linphone_core_media_description_contains_video_stream(md); @@ -426,6 +438,12 @@ 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_upnp_from_remote_media_description(call, rmd); + linphone_core_update_local_media_description_from_upnp(call->localdesc,call->upnp_session); + } +#endif //BUILD_UPNP linphone_call_update_remote_session_id_and_ver(call); sal_call_accept(call->op); md=sal_call_get_final_media_description(call->op); @@ -522,6 +540,10 @@ static void call_terminated(SalOp *op, const char *from){ if (lc->vtable.display_status!=NULL) lc->vtable.display_status(lc,_("Call terminated.")); +#ifdef BUILD_UPNP + linphone_call_delete_upnp_session(call); +#endif //BUILD_UPNP + linphone_call_set_state(call, LinphoneCallEnd,"Call ended"); } @@ -618,6 +640,11 @@ static void call_failure(SalOp *op, SalError error, SalReason sr, const char *de /*resume to the call that send us the refer automatically*/ linphone_core_resume_call(lc,call->referer); } + +#ifdef BUILD_UPNP + linphone_call_delete_upnp_session(call); +#endif //BUILD_UPNP + if (sr == SalReasonDeclined) { call->reason=LinphoneReasonDeclined; linphone_call_set_state(call,LinphoneCallEnd,"Call declined."); diff --git a/coreapi/linphone_tunnel.cc b/coreapi/linphone_tunnel.cc index c76441f5a..f0de56694 100644 --- a/coreapi/linphone_tunnel.cc +++ b/coreapi/linphone_tunnel.cc @@ -23,9 +23,7 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifdef TUNNEL_ENABLED #include "TunnelManager.hh" -#endif #include "linphone_tunnel.h" #include "linphonecore.h" #include "private.h" @@ -35,67 +33,6 @@ LinphoneTunnel* linphone_core_get_tunnel(LinphoneCore *lc){ return lc->tunnel; } -struct _LinphoneTunnelConfig { - char *host; - int port; - int remote_udp_mirror_port; - int delay; -}; - -LinphoneTunnelConfig *linphone_tunnel_config_new() { - LinphoneTunnelConfig *ltc = ms_new0(LinphoneTunnelConfig,1); - ltc->remote_udp_mirror_port = 12345; - ltc->delay = 1000; - return ltc; -} - -void linphone_tunnel_config_set_host(LinphoneTunnelConfig *tunnel, const char *host) { - if(tunnel->host != NULL) { - ms_free(tunnel->host); - tunnel->host = NULL; - } - if(host != NULL && strlen(host)) { - tunnel->host = ms_strdup(host); - } -} - -const char *linphone_tunnel_config_get_host(LinphoneTunnelConfig *tunnel) { - return tunnel->host; -} - -void linphone_tunnel_config_set_port(LinphoneTunnelConfig *tunnel, int port) { - tunnel->port = port; -} - -int linphone_tunnel_config_get_port(LinphoneTunnelConfig *tunnel) { - return tunnel->port; -} - -void linphone_tunnel_config_set_remote_udp_mirror_port(LinphoneTunnelConfig *tunnel, int remote_udp_mirror_port) { - tunnel->remote_udp_mirror_port = remote_udp_mirror_port; -} - -int linphone_tunnel_config_get_remote_udp_mirror_port(LinphoneTunnelConfig *tunnel) { - return tunnel->remote_udp_mirror_port; -} - -void linphone_tunnel_config_set_delay(LinphoneTunnelConfig *tunnel, int delay) { - tunnel->delay = delay; -} - -int linphone_tunnel_config_get_delay(LinphoneTunnelConfig *tunnel) { - return tunnel->delay; -} - -void linphone_tunnel_config_destroy(LinphoneTunnelConfig *tunnel) { - if(tunnel->host != NULL) { - ms_free(tunnel->host); - } - ms_free(tunnel); -} - -#ifdef TUNNEL_ENABLED - struct _LinphoneTunnel { belledonnecomm::TunnelManager *manager; MSList *config_list; @@ -122,16 +59,16 @@ void linphone_tunnel_destroy(LinphoneTunnel *tunnel){ static char *linphone_tunnel_config_to_string(const LinphoneTunnelConfig *tunnel_config) { char *str = NULL; - if(tunnel_config->remote_udp_mirror_port != -1) { + if(linphone_tunnel_config_get_remote_udp_mirror_port(tunnel_config) != -1) { str = ms_strdup_printf("%s:%d:%d:%d", - tunnel_config->host, - tunnel_config->port, - tunnel_config->remote_udp_mirror_port, - tunnel_config->delay); + linphone_tunnel_config_get_host(tunnel_config), + linphone_tunnel_config_get_port(tunnel_config), + linphone_tunnel_config_get_remote_udp_mirror_port(tunnel_config), + linphone_tunnel_config_get_delay(tunnel_config)); } else { str = ms_strdup_printf("%s:%d", - tunnel_config->host, - tunnel_config->port); + linphone_tunnel_config_get_host(tunnel_config), + linphone_tunnel_config_get_port(tunnel_config)); } return str; } @@ -209,11 +146,14 @@ static void linphone_tunnel_save_config(LinphoneTunnel *tunnel) { static void linphone_tunnel_add_server_intern(LinphoneTunnel *tunnel, LinphoneTunnelConfig *tunnel_config) { - if(tunnel_config->remote_udp_mirror_port == -1) { - bcTunnel(tunnel)->addServer(tunnel_config->host, tunnel_config->port); + if(linphone_tunnel_config_get_remote_udp_mirror_port(tunnel_config) == -1) { + bcTunnel(tunnel)->addServer(linphone_tunnel_config_get_host(tunnel_config), + linphone_tunnel_config_get_port(tunnel_config)); } else { - bcTunnel(tunnel)->addServer(tunnel_config->host, tunnel_config->port, - tunnel_config->remote_udp_mirror_port, tunnel_config->delay); + bcTunnel(tunnel)->addServer(linphone_tunnel_config_get_host(tunnel_config), + linphone_tunnel_config_get_port(tunnel_config), + linphone_tunnel_config_get_remote_udp_mirror_port(tunnel_config), + linphone_tunnel_config_get_delay(tunnel_config)); } tunnel->config_list = ms_list_append(tunnel->config_list, tunnel_config); } @@ -385,60 +325,3 @@ void linphone_tunnel_configure(LinphoneTunnel *tunnel){ linphone_tunnel_enable(tunnel, enabled); } -#else - -/*stubs to avoid to have #ifdef TUNNEL_ENABLED in upper layers*/ - -void linphone_tunnel_destroy(LinphoneTunnel *tunnel){ -} - - -void linphone_tunnel_add_server(LinphoneTunnel *tunnel, LinphoneTunnelConfig *tunnel_config){ -} - -void linphone_tunnel_remove_server(LinphoneTunnel *tunnel, LinphoneTunnelConfig *tunnel_config){ -} - -const MSList *linphone_tunnel_get_servers(LinphoneTunnel *tunnel){ - return NULL; -} - -void linphone_tunnel_clean_servers(LinphoneTunnel *tunnel){ -} - -void linphone_tunnel_enable(LinphoneTunnel *tunnel, bool_t enabled){ -} - -bool_t linphone_tunnel_enabled(LinphoneTunnel *tunnel){ - return FALSE; -} - - -void linphone_tunnel_enable_logs_with_handler(LinphoneTunnel *tunnel, bool_t enabled, OrtpLogFunc logHandler){ -} - -void linphone_tunnel_set_http_proxy_auth_info(LinphoneTunnel *tunnel, const char* username,const char* passwd){ -} - -void linphone_tunnel_set_http_proxy(LinphoneTunnel*tunnel, const char *host, int port, const char* username,const char* passwd){ -} - -void linphone_tunnel_get_http_proxy(LinphoneTunnel*tunnel,const char **host, int *port, const char **username, const char **passwd){ -} - -void linphone_tunnel_reconnect(LinphoneTunnel *tunnel){ -} - -void linphone_tunnel_auto_detect(LinphoneTunnel *tunnel){ -} - -void linphone_tunnel_configure(LinphoneTunnel *tunnel){ -} - - -#endif - - - - - diff --git a/coreapi/linphone_tunnel.h b/coreapi/linphone_tunnel.h index 03c568e4e..e42a054dc 100644 --- a/coreapi/linphone_tunnel.h +++ b/coreapi/linphone_tunnel.h @@ -68,7 +68,7 @@ void linphone_tunnel_config_set_host(LinphoneTunnelConfig *tunnel, const char *h * * @param tunnel configuration object */ -const char *linphone_tunnel_config_get_host(LinphoneTunnelConfig *tunnel); +const char *linphone_tunnel_config_get_host(const LinphoneTunnelConfig *tunnel); /** * Set tls port of server. @@ -83,7 +83,7 @@ void linphone_tunnel_config_set_port(LinphoneTunnelConfig *tunnel, int port); * * @param tunnel configuration object */ -int linphone_tunnel_config_get_port(LinphoneTunnelConfig *tunnel); +int linphone_tunnel_config_get_port(const LinphoneTunnelConfig *tunnel); /** * Set the remote port on the tunnel server side used to test udp reachability. @@ -98,7 +98,7 @@ void linphone_tunnel_config_set_remote_udp_mirror_port(LinphoneTunnelConfig *tun * * @param tunnel configuration object */ -int linphone_tunnel_config_get_remote_udp_mirror_port(LinphoneTunnelConfig *tunnel); +int linphone_tunnel_config_get_remote_udp_mirror_port(const LinphoneTunnelConfig *tunnel); /** * Set the udp packet round trip delay in ms for a tunnel configuration. @@ -113,7 +113,7 @@ void linphone_tunnel_config_set_delay(LinphoneTunnelConfig *tunnel, int delay); * * @param tunnel configuration object */ -int linphone_tunnel_config_get_delay(LinphoneTunnelConfig *tunnel); +int linphone_tunnel_config_get_delay(const LinphoneTunnelConfig *tunnel); /** * Destroy a tunnel configuration diff --git a/coreapi/linphone_tunnel_config.c b/coreapi/linphone_tunnel_config.c new file mode 100644 index 000000000..f38568016 --- /dev/null +++ b/coreapi/linphone_tunnel_config.c @@ -0,0 +1,83 @@ +/*************************************************************************** + * linphone_tunnel_config.c + * + * Copyright 2012 Belledonne Communications + ****************************************************************************/ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "linphone_tunnel.h" + +struct _LinphoneTunnelConfig { + char *host; + int port; + int remote_udp_mirror_port; + int delay; +}; + +LinphoneTunnelConfig *linphone_tunnel_config_new() { + LinphoneTunnelConfig *ltc = ms_new0(LinphoneTunnelConfig,1); + ltc->remote_udp_mirror_port = 12345; + ltc->delay = 1000; + return ltc; +} + +void linphone_tunnel_config_set_host(LinphoneTunnelConfig *tunnel, const char *host) { + if(tunnel->host != NULL) { + ms_free(tunnel->host); + tunnel->host = NULL; + } + if(host != NULL && strlen(host)) { + tunnel->host = ms_strdup(host); + } +} + +const char *linphone_tunnel_config_get_host(const LinphoneTunnelConfig *tunnel) { + return tunnel->host; +} + +void linphone_tunnel_config_set_port(LinphoneTunnelConfig *tunnel, int port) { + tunnel->port = port; +} + +int linphone_tunnel_config_get_port(const LinphoneTunnelConfig *tunnel) { + return tunnel->port; +} + +void linphone_tunnel_config_set_remote_udp_mirror_port(LinphoneTunnelConfig *tunnel, int remote_udp_mirror_port) { + tunnel->remote_udp_mirror_port = remote_udp_mirror_port; +} + +int linphone_tunnel_config_get_remote_udp_mirror_port(const LinphoneTunnelConfig *tunnel) { + return tunnel->remote_udp_mirror_port; +} + +void linphone_tunnel_config_set_delay(LinphoneTunnelConfig *tunnel, int delay) { + tunnel->delay = delay; +} + +int linphone_tunnel_config_get_delay(const LinphoneTunnelConfig *tunnel) { + return tunnel->delay; +} + +void linphone_tunnel_config_destroy(LinphoneTunnelConfig *tunnel) { + if(tunnel->host != NULL) { + ms_free(tunnel->host); + } + ms_free(tunnel); +} + diff --git a/coreapi/linphone_tunnel_stubs.c b/coreapi/linphone_tunnel_stubs.c new file mode 100644 index 000000000..0560b3956 --- /dev/null +++ b/coreapi/linphone_tunnel_stubs.c @@ -0,0 +1,83 @@ +/*************************************************************************** + * linphone_tunnel.cc + * + * Fri Dec 9, 2011 + * Copyright 2011 Belledonne Communications + * Author: Guillaume Beraudo + * Email: guillaume dot beraudo at linphone dot org + ****************************************************************************/ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "linphone_tunnel.h" +#include "linphonecore.h" +#include "private.h" +#include "lpconfig.h" + + +LinphoneTunnel* linphone_core_get_tunnel(LinphoneCore *lc){ + return lc->tunnel; +} + +/*stubs to avoid to have #ifdef TUNNEL_ENABLED in upper layers*/ + +void linphone_tunnel_destroy(LinphoneTunnel *tunnel){ +} + + +void linphone_tunnel_add_server(LinphoneTunnel *tunnel, LinphoneTunnelConfig *tunnel_config){ +} + +void linphone_tunnel_remove_server(LinphoneTunnel *tunnel, LinphoneTunnelConfig *tunnel_config){ +} + +const MSList *linphone_tunnel_get_servers(LinphoneTunnel *tunnel){ + return NULL; +} + +void linphone_tunnel_clean_servers(LinphoneTunnel *tunnel){ +} + +void linphone_tunnel_enable(LinphoneTunnel *tunnel, bool_t enabled){ +} + +bool_t linphone_tunnel_enabled(LinphoneTunnel *tunnel){ + return FALSE; +} + + +void linphone_tunnel_enable_logs_with_handler(LinphoneTunnel *tunnel, bool_t enabled, OrtpLogFunc logHandler){ +} + +void linphone_tunnel_set_http_proxy_auth_info(LinphoneTunnel *tunnel, const char* username,const char* passwd){ +} + +void linphone_tunnel_set_http_proxy(LinphoneTunnel*tunnel, const char *host, int port, const char* username,const char* passwd){ +} + +void linphone_tunnel_get_http_proxy(LinphoneTunnel*tunnel,const char **host, int *port, const char **username, const char **passwd){ +} + +void linphone_tunnel_reconnect(LinphoneTunnel *tunnel){ +} + +void linphone_tunnel_auto_detect(LinphoneTunnel *tunnel){ +} + +void linphone_tunnel_configure(LinphoneTunnel *tunnel){ +} + diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index d918057e9..11be247c2 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -302,6 +302,12 @@ 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); + linphone_core_update_upnp_state_in_call_stats(call); + } +#endif //BUILD_UPNP linphone_address_destroy(addr); call->localdesc=md; if (old_md) sal_media_description_unref(old_md); @@ -435,6 +441,11 @@ void linphone_call_init_stats(LinphoneCallStats *stats, int type) { stats->received_rtcp = NULL; stats->sent_rtcp = NULL; stats->ice_state = LinphoneIceStateNotActivated; +#ifdef BUILD_UPNP + stats->upnp_state = LinphoneUpnpStateIdle; +#else + stats->upnp_state = LinphoneUpnpStateNotAvailable; +#endif //BUILD_UPNP } @@ -468,6 +479,11 @@ LinphoneCall * linphone_call_new_outgoing(struct _LinphoneCore *lc, LinphoneAddr if (linphone_core_get_firewall_policy(call->core) == LinphonePolicyUseStun) { call->ping_time=linphone_core_run_stun_tests(call->core,call); } +#ifdef BUILD_UPNP + if (linphone_core_get_firewall_policy(call->core) == LinphonePolicyUseUpnp) { + call->upnp_session = linphone_upnp_session_new(call); + } +#endif //BUILD_UPNP call->camera_active=params->has_video; discover_mtu(lc,linphone_address_get_domain (to)); @@ -529,6 +545,19 @@ LinphoneCall * linphone_call_new_incoming(LinphoneCore *lc, LinphoneAddress *fro case LinphonePolicyUseStun: call->ping_time=linphone_core_run_stun_tests(call->core,call); /* No break to also destroy ice session in this case. */ + break; + case LinphonePolicyUseUpnp: +#ifdef BUILD_UPNP + call->upnp_session = linphone_upnp_session_new(call); + if (call->upnp_session != NULL) { + linphone_call_init_media_streams(call); + if (linphone_core_update_upnp_from_remote_media_description(call, sal_call_get_remote_media_description(op))<0) { + /* uPnP port mappings failed, proceed with the call anyway. */ + linphone_call_delete_upnp_session(call); + } + } +#endif //BUILD_UPNP + break; default: break; } @@ -677,6 +706,9 @@ void linphone_call_set_state(LinphoneCall *call, LinphoneCallState cstate, const static void linphone_call_destroy(LinphoneCall *obj) { +#ifdef BUILD_UPNP + linphone_call_delete_upnp_session(obj); +#endif //BUILD_UPNP linphone_call_delete_ice_session(obj); if (obj->op!=NULL) { sal_op_release(obj->op); @@ -1746,6 +1778,15 @@ void linphone_call_delete_ice_session(LinphoneCall *call){ } } +#ifdef BUILD_UPNP +void linphone_call_delete_upnp_session(LinphoneCall *call){ + if(call->upnp_session!=NULL) { + linphone_upnp_session_destroy(call->upnp_session); + call->upnp_session=NULL; + } +} +#endif //BUILD_UPNP + static void linphone_call_log_fill_stats(LinphoneCallLog *log, AudioStream *st){ audio_stream_get_local_rtp_stats (st,&log->local_stats); log->quality=audio_stream_get_average_quality_rating(st); @@ -2056,6 +2097,11 @@ void linphone_call_background_tasks(LinphoneCall *call, bool_t one_second_elapse report_bandwidth(call,as,vs); ms_message("Thread processing load: audio=%f\tvideo=%f",audio_load,video_load); } + +#ifdef BUILD_UPNP + linphone_upnp_call_process(call); +#endif //BUILD_UPNP + #ifdef VIDEO_ENABLED if (call->videostream!=NULL) { OrtpEvent *ev; diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 20788eb16..c6a2857fc 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -67,7 +67,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*/ @@ -1237,6 +1236,9 @@ static void linphone_core_init (LinphoneCore * lc, const LinphoneCoreVTable *vta lc->tunnel=linphone_core_tunnel_new(lc); if (lc->tunnel) linphone_tunnel_configure(lc->tunnel); #endif +#ifdef BUILD_UPNP + lc->upnp = linphone_upnp_context_new(lc); +#endif //BUILD_UPNP if (lc->vtable.display_status) lc->vtable.display_status(lc,_("Ready")); lc->auto_net_state_mon=lc->sip_conf.auto_net_state_mon; @@ -1332,6 +1334,14 @@ void linphone_core_get_local_ip(LinphoneCore *lc, const char *dest, char *result strncpy(result,ip,LINPHONE_IPADDR_SIZE); return; } +#ifdef BUILD_UPNP + else if (lc->upnp != NULL && linphone_core_get_firewall_policy(lc)==LinphonePolicyUseUpnp && + linphone_upnp_context_get_state(lc->upnp) == LinphoneUpnpStateOk) { + ip = linphone_upnp_context_get_external_ipaddress(lc->upnp); + strncpy(result,ip,LINPHONE_IPADDR_SIZE); + return; + } +#endif //BUILD_UPNP 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 */ @@ -2025,6 +2035,12 @@ void linphone_core_iterate(LinphoneCore *lc){ linphone_call_delete_ice_session(call); linphone_call_stop_media_streams_for_ice_gathering(call); } +#ifdef BUILD_UPNP + if (call->upnp_session != NULL) { + ms_warning("uPnP mapping has not finished yet, proceeded with the call without uPnP anyway."); + linphone_call_delete_upnp_session(call); + } +#endif //BUILD_UPNP linphone_core_start_invite(lc,call); } if (call->state==LinphoneCallIncomingReceived){ @@ -2280,6 +2296,7 @@ static char *get_fixed_contact(LinphoneCore *lc, LinphoneCall *call , LinphonePr int linphone_core_proceed_with_invite_if_ready(LinphoneCore *lc, LinphoneCall *call, LinphoneProxyConfig *dest_proxy){ bool_t ice_ready = FALSE; + bool_t upnp_ready = FALSE; bool_t ping_ready = FALSE; if (call->ice_session != NULL) { @@ -2287,13 +2304,20 @@ int linphone_core_proceed_with_invite_if_ready(LinphoneCore *lc, LinphoneCall *c } else { ice_ready = TRUE; } +#ifdef BUILD_UPNP + if (call->upnp_session != NULL) { + if (linphone_upnp_session_get_state(call->upnp_session) == LinphoneUpnpStateOk) upnp_ready = TRUE; + } else { + upnp_ready = TRUE; + } +#endif //BUILD_UPNP if (call->ping_op != NULL) { if (call->ping_replied == TRUE) ping_ready = TRUE; } else { ping_ready = TRUE; } - if ((ice_ready == TRUE) && (ping_ready == TRUE)) { + if ((ice_ready == TRUE) && (upnp_ready == TRUE) && (ping_ready == TRUE)) { return linphone_core_start_invite(lc, call); } return 0; @@ -2441,7 +2465,7 @@ LinphoneCall * linphone_core_invite_address_with_params(LinphoneCore *lc, const LinphoneAddress *parsed_url2=NULL; char *real_url=NULL; LinphoneCall *call; - bool_t use_ice = FALSE; + bool_t defer = FALSE; linphone_core_preempt_sound_resources(lc); @@ -2494,9 +2518,21 @@ LinphoneCall * linphone_core_invite_address_with_params(LinphoneCore *lc, const linphone_call_delete_ice_session(call); linphone_call_stop_media_streams_for_ice_gathering(call); } else { - use_ice = TRUE; + defer = TRUE; } } + else if (linphone_core_get_firewall_policy(call->core) == LinphonePolicyUseUpnp) { +#ifdef BUILD_UPNP + linphone_call_init_media_streams(call); + call->start_time=time(NULL); + if (linphone_core_update_upnp(lc,call)<0) { + /* uPnP port mappings failed, proceed with the call anyway. */ + linphone_call_delete_upnp_session(call); + } else { + defer = TRUE; + } +#endif //BUILD_UPNP + } if (call->dest_proxy==NULL && lc->sip_conf.ping_with_options==TRUE){ /*defer the start of the call after the OPTIONS ping*/ @@ -2506,7 +2542,7 @@ LinphoneCall * linphone_core_invite_address_with_params(LinphoneCore *lc, const sal_op_set_user_pointer(call->ping_op,call); call->start_time=time(NULL); }else{ - if (use_ice==FALSE) linphone_core_start_invite(lc,call); + if (defer==FALSE) linphone_core_start_invite(lc,call); } if (real_url!=NULL) ms_free(real_url); @@ -2663,9 +2699,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 //BUILD_UPNP if (call->params.in_conference){ subject="Conference"; }else{ @@ -2697,21 +2738,53 @@ int linphone_core_update_call(LinphoneCore *lc, LinphoneCall *call, const Linpho linphone_call_set_state(call,LinphoneCallUpdating,"Updating call"); #ifdef VIDEO_ENABLED bool_t has_video = call->params.has_video; - if ((call->ice_session != NULL) && (call->videostream != NULL) && !params->has_video) { - ice_session_remove_check_list(call->ice_session, call->videostream->ms.ice_check_list); - call->videostream->ms.ice_check_list = NULL; + + // Video removing + if((call->videostream != NULL) && !params->has_video) { + if (call->ice_session != NULL) { + ice_session_remove_check_list(call->ice_session, call->videostream->ms.ice_check_list); + call->videostream->ms.ice_check_list = NULL; + } +#ifdef BUILD_UPNP + if(call->upnp_session != NULL) { + if (linphone_core_update_upnp(lc, call)<0) { + /* uPnP port mappings failed, proceed with the call anyway. */ + linphone_call_delete_upnp_session(call); + } + } +#endif //BUILD_UPNP } + call->params = *params; linphone_call_make_local_media_description(lc, call); - if ((call->ice_session != NULL) && !has_video && call->params.has_video) { - /* Defer call update until the ICE candidates gathering process has finished. */ - ms_message("Defer call update to gather ICE candidates"); - linphone_call_init_video_stream(call); - video_stream_prepare_video(call->videostream); - if (linphone_core_gather_ice_candidates(lc,call)<0) { - /* Ice candidates gathering failed, proceed with the call anyway. */ - linphone_call_delete_ice_session(call); - } else return err; + + // Video adding + if (!has_video && call->params.has_video) { + if (call->ice_session != NULL) { + /* Defer call update until the ICE candidates gathering process has finished. */ + ms_message("Defer call update to gather ICE candidates"); + linphone_call_init_video_stream(call); + video_stream_prepare_video(call->videostream); + if (linphone_core_gather_ice_candidates(lc,call)<0) { + /* Ice candidates gathering failed, proceed with the call anyway. */ + linphone_call_delete_ice_session(call); + } else { + return err; + } + } +#ifdef BUILD_UPNP + if(call->upnp_session != NULL) { + ms_message("Defer call update to add uPnP port mappings"); + linphone_call_init_video_stream(call); + video_stream_prepare_video(call->videostream); + if (linphone_core_update_upnp(lc, call)<0) { + /* uPnP port mappings failed, proceed with the call anyway. */ + linphone_call_delete_upnp_session(call); + } else { + return err; + } + } +#endif //BUILD_UPNP } #endif err = linphone_core_start_update_call(lc, call); @@ -2761,6 +2834,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 //BUILD_UPNP linphone_call_update_remote_session_id_and_ver(call); sal_call_set_local_media_description(call->op,call->localdesc); sal_call_accept(call->op); @@ -2839,8 +2917,25 @@ int linphone_core_accept_call_update(LinphoneCore *lc, LinphoneCall *call, const } else return 0; } } -#endif +#endif //VIDEO_ENABLED } + +#if BUILD_UPNP + if(call->upnp_session != NULL) { + linphone_core_update_upnp_from_remote_media_description(call, sal_call_get_remote_media_description(call->op)); +#ifdef VIDEO_ENABLED + if ((call->params.has_video) && (call->params.has_video != old_has_video)) { + linphone_call_init_video_stream(call); + video_stream_prepare_video(call->videostream); + if (linphone_core_update_upnp(lc, call)<0) { + /* uPnP update failed, proceed with the call anyway. */ + linphone_call_delete_upnp_session(call); + } else return 0; + } +#endif //VIDEO_ENABLED + } +#endif //BUILD_UPNP + linphone_core_start_accept_call_update(lc, call); return 0; } @@ -2979,6 +3074,11 @@ int linphone_core_abort_call(LinphoneCore *lc, LinphoneCall *call, const char *e lc->ringstream=NULL; } linphone_call_stop_media_streams(call); + +#ifdef BUILD_UPNP + linphone_call_delete_upnp_session(call); +#endif //BUILD_UPNP + if (lc->vtable.display_status!=NULL) lc->vtable.display_status(lc,_("Call aborted") ); linphone_call_set_state(call,LinphoneCallError,error); @@ -2997,6 +3097,11 @@ static void terminate_call(LinphoneCore *lc, LinphoneCall *call){ } linphone_call_stop_media_streams(call); + +#ifdef BUILD_UPNP + linphone_call_delete_upnp_session(call); +#endif //BUILD_UPNP + if (lc->vtable.display_status!=NULL) lc->vtable.display_status(lc,_("Call ended") ); linphone_call_set_state(call,LinphoneCallEnd,"Call terminated"); @@ -3139,8 +3244,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 //BUILD_UPNP if (sal_media_description_has_dir(call->resultdesc,SalStreamSendRecv)){ sal_media_description_set_dir(call->localdesc,SalStreamSendOnly); subject="Call on hold"; @@ -3219,8 +3330,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 //BUILD_UPNP 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"; @@ -4949,6 +5066,12 @@ static void linphone_core_uninit(LinphoneCore *lc) usleep(50000); #endif } + +#ifdef BUILD_UPNP + linphone_upnp_context_destroy(lc->upnp); + lc->upnp = NULL; +#endif //BUILD_UPNP + if (lc->friends) ms_list_for_each(lc->friends,(void (*)(void *))linphone_friend_close_subscriptions); linphone_core_set_state(lc,LinphoneGlobalShutdown,"Shutting down"); diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index 3acac8fde..1f49cce46 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -284,6 +284,27 @@ enum _LinphoneIceState{ **/ typedef enum _LinphoneIceState LinphoneIceState; +/** + * Enum describing uPnP states. + * @ingroup initializing +**/ +enum _LinphoneUpnpState{ + LinphoneUpnpStateIdle, /**< uPnP is not activate */ + LinphoneUpnpStatePending, /**< uPnP process is in progress */ + LinphoneUpnpStateAdding, /**< Internal use: Only used by port binding */ + LinphoneUpnpStateRemoving, /**< Internal use: Only used by port binding */ + LinphoneUpnpStateNotAvailable, /**< uPnP is not available */ + LinphoneUpnpStateOk, /**< uPnP is enabled */ + LinphoneUpnpStateKo, /**< uPnP processing has failed */ +}; + +/** + * Enum describing uPnP states. + * @ingroup initializing +**/ +typedef enum _LinphoneUpnpState LinphoneUpnpState; + + /** * The LinphoneCallStats objects carries various statistic informations regarding quality of audio or video streams. * @@ -309,6 +330,7 @@ struct _LinphoneCallStats { mblk_t* sent_rtcp;/**ifa_addr && ifp->ifa_addr->sa_family == type && (ifp->ifa_flags & UP_FLAG) && !(ifp->ifa_flags & IFF_LOOPBACK)) { - getnameinfo(ifp->ifa_addr, + if(getnameinfo(ifp->ifa_addr, (type == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in), - address, size, NULL, 0, NI_NUMERICHOST); - if (strchr(address, '%') == NULL) { /*avoid ipv6 link-local addresses */ - /*ms_message("getifaddrs() found %s",address);*/ - ret++; - break; + address, size, NULL, 0, NI_NUMERICHOST) == 0) { + if (strchr(address, '%') == NULL) { /*avoid ipv6 link-local addresses */ + /*ms_message("getifaddrs() found %s",address);*/ + ret++; + break; + } } } } @@ -1099,26 +1100,26 @@ static int get_local_ip_for_with_connect(int type, const char *dest, char *resul } int linphone_core_get_local_ip_for(int type, const char *dest, char *result){ - strcpy(result,type==AF_INET ? "127.0.0.1" : "::1"); + strcpy(result,type==AF_INET ? "127.0.0.1" : "::1"); #ifdef HAVE_GETIFADDRS - if (dest==NULL) { - /*we use getifaddrs for lookup of default interface */ - int found_ifs; - - found_ifs=get_local_ip_with_getifaddrs(type,result,LINPHONE_IPADDR_SIZE); - if (found_ifs==1){ - return 0; - }else if (found_ifs<=0){ - /*absolutely no network on this machine */ - return -1; - } - } + if (dest==NULL) { + /*we use getifaddrs for lookup of default interface */ + int found_ifs; + + found_ifs=get_local_ip_with_getifaddrs(type,result,LINPHONE_IPADDR_SIZE); + if (found_ifs==1){ + return 0; + }else if (found_ifs<=0){ + /*absolutely no network on this machine */ + return -1; + } + } #endif - /*else use connect to find the best local ip address */ - if (type==AF_INET) - dest="87.98.157.38"; /*a public IP address*/ - else dest="2a00:1450:8002::68"; - return get_local_ip_for_with_connect(type,dest,result); + /*else use connect to find the best local ip address */ + if (type==AF_INET) + dest="87.98.157.38"; /*a public IP address*/ + else dest="2a00:1450:8002::68"; + return get_local_ip_for_with_connect(type,dest,result); } #ifndef WIN32 diff --git a/coreapi/private.h b/coreapi/private.h index edb717ae3..c421b2bc6 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -28,9 +28,11 @@ extern "C" { #endif #include "linphonecore.h" +#include "linphonefriend.h" #include "linphone_tunnel.h" #include "linphonecore_utils.h" #include "sal.h" +#include "sipsetup.h" #ifdef HAVE_CONFIG_H #include "config.h" @@ -38,6 +40,9 @@ extern "C" { #include "mediastreamer2/ice.h" #include "mediastreamer2/mediastream.h" #include "mediastreamer2/msconference.h" +#ifdef BUILD_UPNP +#include "upnp.h" +#endif //BUILD_UPNP #ifndef LIBLINPHONE_VERSION #define LIBLINPHONE_VERSION LINPHONE_VERSION @@ -145,6 +150,9 @@ struct _LinphoneCall OrtpEvQueue *videostream_app_evq; CallCallbackObj nextVideoFrameDecoded; LinphoneCallStats stats[2]; +#ifdef BUILD_UPNP + UpnpSession *upnp_session; +#endif //BUILD_UPNP IceSession *ice_session; LinphoneChatMessage* pending_message; int ping_time; @@ -286,6 +294,7 @@ void linphone_call_stop_audio_stream(LinphoneCall *call); void linphone_call_stop_video_stream(LinphoneCall *call); void linphone_call_stop_media_streams(LinphoneCall *call); void linphone_call_delete_ice_session(LinphoneCall *call); +void linphone_call_delete_upnp_session(LinphoneCall *call); void linphone_call_stop_media_streams_for_ice_gathering(LinphoneCall *call); void linphone_call_update_crypto_parameters(LinphoneCall *call, SalMediaDescription *old_md, SalMediaDescription *new_md); void linphone_call_update_remote_session_id_and_ver(LinphoneCall *call); @@ -565,8 +574,8 @@ struct _LinphoneCore bool_t network_reachable; bool_t use_preview_window; - time_t network_last_check; - bool_t network_last_status; + time_t network_last_check; + bool_t network_last_status; bool_t ringstream_autorelease; bool_t pad[3]; @@ -575,13 +584,16 @@ struct _LinphoneCore LinphoneTunnel *tunnel; char* device_id; MSList *last_recv_msg_ids; +#ifdef BUILD_UPNP + UpnpContext *upnp; +#endif //BUILD_UPNP }; LinphoneTunnel *linphone_core_tunnel_new(LinphoneCore *lc); void linphone_tunnel_destroy(LinphoneTunnel *tunnel); void linphone_tunnel_configure(LinphoneTunnel *tunnel); void linphone_tunnel_enable_logs_with_handler(LinphoneTunnel *tunnel, bool_t enabled, OrtpLogFunc logHandler); - + bool_t linphone_core_can_we_add_call(LinphoneCore *lc); int linphone_core_add_call( LinphoneCore *lc, LinphoneCall *call); int linphone_core_del_call( LinphoneCore *lc, LinphoneCall *call); @@ -649,6 +661,9 @@ void call_logs_write_to_config_file(LinphoneCore *lc); int linphone_core_get_edge_bw(LinphoneCore *lc); int linphone_core_get_edge_ptime(LinphoneCore *lc); +int linphone_upnp_init(LinphoneCore *lc); +void linphone_upnp_destroy(LinphoneCore *lc); + #ifdef __cplusplus } #endif diff --git a/coreapi/proxy.c b/coreapi/proxy.c index a109a3be2..bb6c8ec6a 100644 --- a/coreapi/proxy.c +++ b/coreapi/proxy.c @@ -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™te 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Žunion 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‹o TomŽ and Pr’ncipe" ,"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" }, diff --git a/coreapi/upnp.c b/coreapi/upnp.c new file mode 100644 index 000000000..a05e1e0f2 --- /dev/null +++ b/coreapi/upnp.c @@ -0,0 +1,1058 @@ +/* +linphone +Copyright (C) 2012 Belledonne Communications SARL + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "upnp.h" +#include "private.h" +#include "lpconfig.h" + +#define UPNP_ADD_MAX_RETRY 4 +#define UPNP_REMOVE_MAX_RETRY 4 +#define UPNP_SECTION_NAME "uPnP" + +/* + * uPnP Definitions + */ + +typedef struct _UpnpPortBinding { + ms_mutex_t mutex; + LinphoneUpnpState state; + upnp_igd_ip_protocol protocol; + char local_addr[LINPHONE_IPADDR_SIZE]; + int local_port; + char external_addr[LINPHONE_IPADDR_SIZE]; + int external_port; + int retry; + int ref; + bool_t to_remove; + bool_t to_add; +} UpnpPortBinding; + +typedef struct _UpnpStream { + UpnpPortBinding *rtp; + UpnpPortBinding *rtcp; + LinphoneUpnpState state; +} UpnpStream; + +struct _UpnpSession { + LinphoneCall *call; + UpnpStream *audio; + UpnpStream *video; + LinphoneUpnpState state; +}; + +struct _UpnpContext { + LinphoneCore *lc; + upnp_igd_context *upnp_igd_ctxt; + UpnpPortBinding *sip_tcp; + UpnpPortBinding *sip_tls; + UpnpPortBinding *sip_udp; + LinphoneUpnpState state; + MSList *removing_configs; + MSList *adding_configs; + MSList *pending_bindings; + + ms_mutex_t mutex; + ms_cond_t empty_cond; + +}; + + +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_copy(const UpnpPortBinding *port); +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_port_binding_log(int level, const char *msg, const UpnpPortBinding *port); +void linphone_upnp_port_binding_release(UpnpPortBinding *port); + +MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc); +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 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; + switch(level) { + case UPNP_IGD_MESSAGE: + ortp_level = ORTP_MESSAGE; + break; + case UPNP_IGD_WARNING: + ortp_level = ORTP_DEBUG; // Too verbose otherwise + break; + case UPNP_IGD_ERROR: + ortp_level = ORTP_DEBUG; // Too verbose otherwise + break; + default: + break; + } + ortp_logv(ortp_level, fmt, list); +} + +void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) { + UpnpContext *lupnp = (UpnpContext *)cookie; + upnp_igd_port_mapping *mapping = NULL; + UpnpPortBinding *port_mapping = NULL; + const char *ip_address = NULL; + const char *connection_status = NULL; + bool_t nat_enabled = FALSE; + LinphoneUpnpState old_state; + + if(lupnp == NULL || lupnp->upnp_igd_ctxt == NULL) { + ms_error("uPnP IGD: Invalid context in callback"); + return; + } + + ms_mutex_lock(&lupnp->mutex); + old_state = lupnp->state; + + switch(event) { + case UPNP_IGD_EXTERNAL_IPADDRESS_CHANGED: + case UPNP_IGD_NAT_ENABLED_CHANGED: + case UPNP_IGD_CONNECTION_STATUS_CHANGED: + ip_address = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt); + connection_status = upnp_igd_get_connection_status(lupnp->upnp_igd_ctxt); + nat_enabled = upnp_igd_get_nat_enabled(lupnp->upnp_igd_ctxt); + + if(ip_address == NULL || connection_status == NULL) { + ms_message("uPnP IGD: Pending"); + lupnp->state = LinphoneUpnpStatePending; + } else if(strcasecmp(connection_status, "Connected") || !nat_enabled) { + ms_message("uPnP IGD: Not Available"); + lupnp->state = LinphoneUpnpStateNotAvailable; + } else { + ms_message("uPnP IGD: Connected"); + lupnp->state = LinphoneUpnpStateOk; + if(old_state != LinphoneUpnpStateOk) { + linphone_core_upnp_refresh(lupnp); + } + } + + break; + + case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS: + mapping = (upnp_igd_port_mapping *) arg; + port_mapping = (UpnpPortBinding*) mapping->cookie; + port_mapping->external_port = mapping->remote_port; + port_mapping->state = LinphoneUpnpStateOk; + linphone_upnp_port_binding_log(ORTP_MESSAGE, "Added port binding", port_mapping); + linphone_upnp_config_add_port_binding(lupnp, port_mapping); + + break; + + case UPNP_IGD_PORT_MAPPING_ADD_FAILURE: + mapping = (upnp_igd_port_mapping *) arg; + port_mapping = (UpnpPortBinding*) mapping->cookie; + port_mapping->external_port = -1; //Force random external port + if(linphone_upnp_context_send_add_port_binding(lupnp, port_mapping) != 0) { + linphone_upnp_port_binding_log(ORTP_ERROR, "Can't add port binding", port_mapping); + } + + break; + + case UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS: + mapping = (upnp_igd_port_mapping *) arg; + port_mapping = (UpnpPortBinding*) mapping->cookie; + port_mapping->state = LinphoneUpnpStateIdle; + linphone_upnp_port_binding_log(ORTP_MESSAGE, "Removed port binding", port_mapping); + linphone_upnp_config_remove_port_binding(lupnp, port_mapping); + + break; + + case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE: + mapping = (upnp_igd_port_mapping *) arg; + port_mapping = (UpnpPortBinding*) mapping->cookie; + if(linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping) != 0) { + linphone_upnp_port_binding_log(ORTP_ERROR, "Can't remove port binding", port_mapping); + linphone_upnp_config_remove_port_binding(lupnp, port_mapping); + } + + break; + + default: + break; + } + + if(port_mapping != NULL) { + /* + * Execute delayed actions + */ + if(port_mapping->to_remove) { + if(port_mapping->state == LinphoneUpnpStateOk) { + port_mapping->to_remove = FALSE; + linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping); + } else if(port_mapping->state == LinphoneUpnpStateKo) { + port_mapping->to_remove = FALSE; + } + } + if(port_mapping->to_add) { + if(port_mapping->state == LinphoneUpnpStateIdle || port_mapping->state == LinphoneUpnpStateKo) { + port_mapping->to_add = FALSE; + linphone_upnp_context_send_add_port_binding(lupnp, port_mapping); + } + } + + lupnp->pending_bindings = ms_list_remove(lupnp->pending_bindings, port_mapping); + linphone_upnp_port_binding_release(port_mapping); + } + + /* + * If there is no pending binding emit a signal + */ + if(lupnp->pending_bindings == NULL) { + pthread_cond_signal(&lupnp->empty_cond); + } + ms_mutex_unlock(&lupnp->mutex); +} + + +/** + * uPnP Context + */ + +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->lc = lc; + lupnp->pending_bindings = NULL; + lupnp->adding_configs = NULL; + lupnp->removing_configs = NULL; + lupnp->state = LinphoneUpnpStateIdle; + ms_message("uPnP IGD: New %p for core %p", lupnp, lc); + + 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; + } + + 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); + 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); + + return lupnp; +} + +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); + */ + + 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; + } + + /* Wait all pending bindings are done */ + if(lupnp->pending_bindings != NULL) { + ms_message("uPnP IGD: Wait all pending port bindings ..."); + ms_cond_wait(&lupnp->empty_cond, &lupnp->mutex); + } + ms_mutex_unlock(&lupnp->mutex); + + if(lupnp->upnp_igd_ctxt != NULL) { + upnp_igd_destroy(lupnp->upnp_igd_ctxt); + } + + /* Run one time the hook for configuration update */ + linphone_core_upnp_hook(lupnp); + + /* Release port bindings */ + if(lupnp->sip_udp != NULL) { + linphone_upnp_port_binding_release(lupnp->sip_udp); + lupnp->sip_udp = NULL; + } + if(lupnp->sip_tcp != NULL) { + linphone_upnp_port_binding_release(lupnp->sip_tcp); + lupnp->sip_tcp = NULL; + } + if(lupnp->sip_tls != NULL) { + linphone_upnp_port_binding_release(lupnp->sip_tls); + lupnp->sip_tcp = NULL; + } + + /* Release lists */ + ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release); + lupnp->adding_configs = ms_list_free(lupnp->adding_configs); + ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release); + lupnp->removing_configs = ms_list_free(lupnp->removing_configs); + ms_list_for_each(lupnp->pending_bindings,(void (*)(void*))linphone_upnp_port_binding_release); + lupnp->pending_bindings = ms_list_free(lupnp->pending_bindings); + + ms_mutex_destroy(&lupnp->mutex); + ms_cond_destroy(&lupnp->empty_cond); + + ms_message("uPnP IGD: destroy %p", lupnp); + ms_free(lupnp); +} + +LinphoneUpnpState linphone_upnp_context_get_state(UpnpContext *ctx) { + return ctx->state; +} + +const char* linphone_upnp_context_get_external_ipaddress(UpnpContext *ctx) { + return upnp_igd_get_external_ipaddress(ctx->upnp_igd_ctxt); +} + +int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port) { + upnp_igd_port_mapping mapping; + char description[128]; + int ret; + + // Compute port binding state + if(port->state != LinphoneUpnpStateAdding) { + port->to_remove = FALSE; + switch(port->state) { + case LinphoneUpnpStateKo: + case LinphoneUpnpStateIdle: { + port->retry = 0; + port->state = LinphoneUpnpStateAdding; + } + break; + case LinphoneUpnpStateRemoving: { + port->to_add = TRUE; + return 0; + } + break; + default: + return 0; + } + } + + if(port->retry >= UPNP_ADD_MAX_RETRY) { + ret = -1; + } else { + 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; + mapping.remote_host = ""; + snprintf(description, 128, "%s %s at %s:%d", + PACKAGE_NAME, + (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP": "UDP", + port->local_addr, port->local_port); + mapping.description = description; + mapping.protocol = port->protocol; + + port->retry++; + linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to add port binding", port); + ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping); + } + if(ret != 0) { + port->state = LinphoneUpnpStateKo; + } + return ret; +} + +int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port) { + upnp_igd_port_mapping mapping; + int ret; + + // Compute port binding state + if(port->state != LinphoneUpnpStateRemoving) { + port->to_add = FALSE; + switch(port->state) { + case LinphoneUpnpStateOk: { + port->retry = 0; + port->state = LinphoneUpnpStateRemoving; + } + break; + case LinphoneUpnpStateAdding: { + port->to_remove = TRUE; + return 0; + } + break; + default: + return 0; + } + } + + if(port->retry >= UPNP_REMOVE_MAX_RETRY) { + ret = -1; + } else { + mapping.cookie = linphone_upnp_port_binding_retain(port); + lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie); + + mapping.remote_port = port->external_port; + mapping.remote_host = ""; + mapping.protocol = port->protocol; + port->retry++; + linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to remove port binding", port); + ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping); + } + if(ret != 0) { + port->state = LinphoneUpnpStateKo; + } + return ret; +} + +/* + * uPnP Core interfaces + */ + +int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool_t video) { + LinphoneCore *lc = call->core; + UpnpContext *lupnp = lc->upnp; + int ret = -1; + 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); + } + + /* + * 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); + } + } + + ms_mutex_unlock(&lupnp->mutex); + + /* + * Update uPnP call state + */ + linphone_upnp_call_process(call); + + return ret; +} + + +int linphone_core_update_upnp_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) { + bool_t audio = FALSE; + bool_t video = FALSE; + int i; + const SalStreamDescription *stream; + + for (i = 0; i < md->nstreams; i++) { + stream = &md->streams[i]; + if(stream->type == SalAudio) { + audio = TRUE; + } else if(stream->type == SalVideo) { + video = TRUE; + } + } + + return linphone_core_update_upnp_audio_video(call, audio, video); +} + +int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) { + return linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL); +} + +void linphone_core_update_upnp_state_in_call_stats(LinphoneCall *call) { + call->stats[LINPHONE_CALL_STATS_AUDIO].upnp_state = call->upnp_session->audio->state; + call->stats[LINPHONE_CALL_STATS_VIDEO].upnp_state = call->upnp_session->video->state; +} + +int linphone_upnp_call_process(LinphoneCall *call) { + LinphoneCore *lc = call->core; + UpnpContext *lupnp = lc->upnp; + int ret = -1; + LinphoneUpnpState oldState = 0, newState = 0; + + if(lupnp == NULL) { + return ret; + } + + ms_mutex_lock(&lupnp->mutex); + + // Don't handle when the call + if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) { + ret = 0; + + /* + * 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; + } + + /* + * 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; + } + + /* + * Update session state + */ + oldState = call->upnp_session->state; + if(call->upnp_session->audio->state == LinphoneUpnpStateOk && + call->upnp_session->video->state == LinphoneUpnpStateOk) { + call->upnp_session->state = LinphoneUpnpStateOk; + } else if(call->upnp_session->audio->state == LinphoneUpnpStatePending || + call->upnp_session->video->state == LinphoneUpnpStatePending) { + call->upnp_session->state = LinphoneUpnpStatePending; + } else if(call->upnp_session->audio->state == LinphoneUpnpStateKo || + call->upnp_session->video->state == LinphoneUpnpStateKo) { + call->upnp_session->state = LinphoneUpnpStateKo; + } else { + call->upnp_session->state = LinphoneUpnpStateIdle; + } + newState = call->upnp_session->state; + + /* 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); + } + + return ret; +} + +void linphone_core_upnp_refresh(UpnpContext *lupnp) { + MSList *global_list = NULL; + MSList *list = NULL; + MSList *item; + LinphoneCall *call; + UpnpPortBinding *port_mapping, *port_mapping2; + + ms_message("uPnP IGD: Refresh mappings"); + + /* Remove context port bindings */ + if(lupnp->sip_udp != NULL) { + global_list = ms_list_append(global_list, lupnp->sip_udp); + } + if(lupnp->sip_tcp != NULL) { + global_list = ms_list_append(global_list, lupnp->sip_tcp); + } + if(lupnp->sip_tls != NULL) { + global_list = ms_list_append(global_list, lupnp->sip_tls); + } + + /* 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); + } + list = list->next; + } + + // Remove port binding configurations + list = linphone_upnp_config_list_port_bindings(lupnp->lc->config); + 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); + } else if(port_mapping2->state == LinphoneUpnpStateIdle){ + /* Force to remove */ + port_mapping2->state = LinphoneUpnpStateOk; + } + } + ms_list_for_each(list, (void (*)(void*))linphone_upnp_port_binding_release); + list = ms_list_free(list); + + + // (Re)Add removed port bindings + list = global_list; + while(list != NULL) { + port_mapping = (UpnpPortBinding *)list->data; + linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping); + linphone_upnp_context_send_add_port_binding(lupnp, port_mapping); + list = list->next; + } + global_list = ms_list_free(global_list); +} + +bool_t linphone_core_upnp_hook(void *data) { + char key[64]; + 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", + (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP", + port_mapping->external_port, + port_mapping->local_port); + lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, "uPnP"); + linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping); + } + ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release); + lupnp->adding_configs = ms_list_free(lupnp->adding_configs); + + /* Remove configs */ + for(item = lupnp->removing_configs;item!=NULL;item=item->next) { + port_mapping = (UpnpPortBinding *)item->data; + snprintf(key, sizeof(key), "%s-%d-%d", + (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP", + port_mapping->external_port, + port_mapping->local_port); + lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, NULL); + linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping); + } + ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release); + lupnp->removing_configs = ms_list_free(lupnp->removing_configs); + + ms_mutex_unlock(&lupnp->mutex); + return TRUE; +} + +int linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) { + int i; + SalStreamDescription *stream; + UpnpStream *upnpStream; + + for (i = 0; i < desc->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; + } + } + } + return 0; +} + + +/* + * uPnP Port Binding + */ + +UpnpPortBinding *linphone_upnp_port_binding_new() { + UpnpPortBinding *port = NULL; + port = ms_new0(UpnpPortBinding,1); + ms_mutex_init(&port->mutex, NULL); + port->state = LinphoneUpnpStateIdle; + port->local_addr[0] = '\0'; + port->local_port = -1; + port->external_addr[0] = '\0'; + port->external_port = -1; + port->to_remove = FALSE; + port->to_add = FALSE; + port->ref = 1; + return port; +} + +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)); + ms_mutex_init(&new_port->mutex, NULL); + new_port->ref = 1; + return new_port; +} + +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, + (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP", + port->external_port, + port->local_addr, + port->local_port); + } else { + ortp_log(level, "uPnP IGD: %s %s|%d->%d", msg, + (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP", + port->external_port, + port->local_port); + } +} + +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; +} + +UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port) { + UpnpPortBinding *port_mapping; + while(list != NULL) { + port_mapping = (UpnpPortBinding *)list->data; + if(linphone_upnp_port_binding_equal(port, port_mapping)) { + return port_mapping; + } + list = list->next; + } + + return NULL; +} + +UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port) { + ms_mutex_lock(&port->mutex); + port->ref++; + ms_mutex_unlock(&port->mutex); + return port; +} + +void linphone_upnp_port_binding_release(UpnpPortBinding *port) { + ms_mutex_lock(&port->mutex); + if(--port->ref == 0) { + ms_mutex_unlock(&port->mutex); + ms_mutex_destroy(&port->mutex); + ms_free(port); + return; + } + ms_mutex_unlock(&port->mutex); +} + + +/* + * uPnP Stream + */ + +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; + 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; + ms_free(stream); +} + + +/* + * uPnP Session + */ + +UpnpSession* linphone_upnp_session_new(LinphoneCall* call) { + UpnpSession *session = ms_new0(UpnpSession,1); + session->call = call; + session->state = LinphoneUpnpStateIdle; + session->audio = linphone_upnp_stream_new(); + session->video = linphone_upnp_stream_new(); + return session; +} + +void linphone_upnp_session_destroy(UpnpSession *session) { + LinphoneCore *lc = session->call->core; + + 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); + } + + linphone_upnp_stream_destroy(session->audio); + linphone_upnp_stream_destroy(session->video); + ms_free(session); +} + +LinphoneUpnpState linphone_upnp_session_get_state(UpnpSession *session) { + return session->state; +} + +/* + * uPnP Config + */ + +struct linphone_upnp_config_list_port_bindings_struct { + struct _LpConfig *lpc; + MSList *retList; +}; + +static void linphone_upnp_config_list_port_bindings_cb(const char *entry, struct linphone_upnp_config_list_port_bindings_struct *cookie) { + char protocol_str[4]; // TCP or UDP + upnp_igd_ip_protocol protocol; + int external_port; + int local_port; + 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) { + 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 = linphone_upnp_port_binding_new(); + port->state = LinphoneUpnpStateOk; + port->protocol = protocol; + port->external_port = external_port; + port->local_port = local_port; + cookie->retList = ms_list_append(cookie->retList, port); + } + } else { + valid = FALSE; + } + if(!valid) { + ms_warning("uPnP configuration invalid line: %s", entry); + } +} + +MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc) { + struct linphone_upnp_config_list_port_bindings_struct cookie = {lpc, NULL}; + lp_config_for_each_entry(lpc, UPNP_SECTION_NAME, (void(*)(const char *, void*))linphone_upnp_config_list_port_bindings_cb, &cookie); + return cookie.retList; +} + +void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) { + MSList *list; + UpnpPortBinding *list_port; + + list = lupnp->removing_configs; + while(list != NULL) { + list_port = (UpnpPortBinding *)list->data; + if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) { + lupnp->removing_configs = ms_list_remove(lupnp->removing_configs, list_port); + linphone_upnp_port_binding_release(list_port); + return; + } + list = ms_list_next(list); + } + + list = lupnp->adding_configs; + while(list != NULL) { + list_port = (UpnpPortBinding *)list->data; + if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) { + return; + } + list = ms_list_next(list); + } + + list_port = linphone_upnp_port_binding_copy(port); + lupnp->adding_configs = ms_list_append(lupnp->adding_configs, list_port); +} + +void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) { + MSList *list; + UpnpPortBinding *list_port; + + list = lupnp->adding_configs; + while(list != NULL) { + list_port = (UpnpPortBinding *)list->data; + if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) { + lupnp->adding_configs = ms_list_remove(lupnp->adding_configs, list_port); + linphone_upnp_port_binding_release(list_port); + return; + } + list = ms_list_next(list); + } + + list = lupnp->removing_configs; + while(list != NULL) { + list_port = (UpnpPortBinding *)list->data; + if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) { + return; + } + list = ms_list_next(list); + } + + list_port = linphone_upnp_port_binding_copy(port); + lupnp->removing_configs = ms_list_append(lupnp->removing_configs, list_port); +} diff --git a/coreapi/upnp.h b/coreapi/upnp.h new file mode 100644 index 000000000..b3a5b9e49 --- /dev/null +++ b/coreapi/upnp.h @@ -0,0 +1,45 @@ +/* +linphone +Copyright (C) 2012 Belledonne Communications SARL + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef LINPHONE_UPNP_H +#define LINPHONE_UPNP_H + +#include "mediastreamer2/upnp_igd.h" +#include "linphonecore.h" +#include "sal.h" + +typedef struct _UpnpSession UpnpSession; +typedef struct _UpnpContext UpnpContext; + +int linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session); +int linphone_core_update_upnp_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md); +int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call); + +int linphone_upnp_call_process(LinphoneCall *call); +UpnpSession* linphone_upnp_session_new(LinphoneCall *call); +void linphone_upnp_session_destroy(UpnpSession* session); +LinphoneUpnpState linphone_upnp_session_get_state(UpnpSession *session); + +UpnpContext *linphone_upnp_context_new(LinphoneCore *lc); +void linphone_upnp_context_destroy(UpnpContext *ctx); +LinphoneUpnpState linphone_upnp_context_get_state(UpnpContext *ctx); +const char *linphone_upnp_context_get_external_ipaddress(UpnpContext *ctx); +void linphone_core_update_upnp_state_in_call_stats(LinphoneCall *call); + +#endif //LINPHONE_UPNP_H diff --git a/gtk/call_statistics.ui b/gtk/call_statistics.ui index 444710472..6ed8bd9bd 100644 --- a/gtk/call_statistics.ui +++ b/gtk/call_statistics.ui @@ -1,71 +1,34 @@ - + - False 5 Call statistics dialog - + True - False 2 - - - True - False - end - - - - - - gtk-close - True - True - True - False - True - - - False - False - 1 - - - - - False - True - end - 0 - - True - False 0 none True - False 12 True - False 6 2 True True - False Audio codec @@ -75,7 +38,6 @@ True - False Video codec @@ -87,7 +49,6 @@ True - False Audio IP bandwidth usage @@ -99,7 +60,6 @@ True - False 1 @@ -109,7 +69,6 @@ True - False 1 @@ -121,7 +80,6 @@ True - False 1 @@ -133,8 +91,7 @@ True - False - Media connectivity + Audio Media connectivity 4 @@ -143,15 +100,8 @@ - - - - - - - + True - False 1 @@ -163,7 +113,6 @@ True - False Video IP bandwidth usage @@ -175,7 +124,6 @@ True - False 1 @@ -184,6 +132,27 @@ 4 + + + True + Video Media connectivity + + + 5 + 6 + + + + + True + + + 1 + 2 + 5 + 6 + + @@ -191,7 +160,6 @@ True - False <b>Call statistics and information</b> True @@ -203,6 +171,34 @@ 1 + + + True + end + + + + + + gtk-close + True + True + True + True + + + False + False + 1 + + + + + False + end + 0 + + diff --git a/gtk/incall_view.c b/gtk/incall_view.c index 192e92fab..8bf19c19c 100644 --- a/gtk/incall_view.c +++ b/gtk/incall_view.c @@ -231,10 +231,29 @@ static const char *ice_state_to_string(LinphoneIceState ice_state){ return "invalid"; } +static const char *upnp_state_to_string(LinphoneUpnpState ice_state){ + switch(ice_state){ + case LinphoneUpnpStateIdle: + return _("uPnP not activated"); + case LinphoneUpnpStatePending: + return _("uPnP in progress"); + case LinphoneUpnpStateNotAvailable: + return _("uPnp not available"); + case LinphoneUpnpStateOk: + return _("uPnP is running"); + case LinphoneUpnpStateKo: + return _("uPnP failed"); + default: + break; + } + return "invalid"; +} + static void _refresh_call_stats(GtkWidget *callstats, LinphoneCall *call){ const LinphoneCallStats *as=linphone_call_get_audio_stats(call); const LinphoneCallStats *vs=linphone_call_get_video_stats(call); - LinphoneIceState ice_state=as->ice_state; + const char *audio_media_connectivity = _("Direct"); + const char *video_media_connectivity = _("Direct"); gchar *tmp=g_strdup_printf(_("download: %f\nupload: %f (kbit/s)"), as->download_bandwidth,as->upload_bandwidth); gtk_label_set_markup(GTK_LABEL(linphone_gtk_get_widget(callstats,"audio_bandwidth_usage")),tmp); @@ -243,7 +262,18 @@ static void _refresh_call_stats(GtkWidget *callstats, LinphoneCall *call){ vs->download_bandwidth,vs->upload_bandwidth); gtk_label_set_markup(GTK_LABEL(linphone_gtk_get_widget(callstats,"video_bandwidth_usage")),tmp); g_free(tmp); - gtk_label_set_text(GTK_LABEL(linphone_gtk_get_widget(callstats,"media_connectivity")),ice_state_to_string(ice_state)); + if(as->upnp_state != LinphoneUpnpStateNotAvailable && as->upnp_state != LinphoneUpnpStateIdle) { + audio_media_connectivity = upnp_state_to_string(as->upnp_state); + } else if(as->ice_state != LinphoneIceStateNotActivated) { + audio_media_connectivity = ice_state_to_string(as->ice_state); + } + gtk_label_set_text(GTK_LABEL(linphone_gtk_get_widget(callstats,"audio_media_connectivity")),audio_media_connectivity); + if(vs->upnp_state != LinphoneUpnpStateNotAvailable && vs->upnp_state != LinphoneUpnpStateIdle) { + video_media_connectivity = upnp_state_to_string(vs->upnp_state); + } else if(vs->ice_state != LinphoneIceStateNotActivated) { + video_media_connectivity = ice_state_to_string(vs->ice_state); + } + gtk_label_set_text(GTK_LABEL(linphone_gtk_get_widget(callstats,"video_media_connectivity")),video_media_connectivity); } static gboolean refresh_call_stats(GtkWidget *callstats){ diff --git a/gtk/parameters.ui b/gtk/parameters.ui index 130b7e7c5..e4a6430fa 100644 --- a/gtk/parameters.ui +++ b/gtk/parameters.ui @@ -765,6 +765,23 @@ 3 + + + Behind NAT / Firewall (use uPnP) + False + True + True + False + True + no_nat + + + + True + True + 4 + + True diff --git a/gtk/propertybox.c b/gtk/propertybox.c index 41fe854ed..c2258ea57 100644 --- a/gtk/propertybox.c +++ b/gtk/propertybox.c @@ -236,6 +236,11 @@ void linphone_gtk_use_ice_toggled(GtkWidget *w){ linphone_core_set_firewall_policy(linphone_gtk_get_core(),LinphonePolicyUseIce); } +void linphone_gtk_use_upnp_toggled(GtkWidget *w){ + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w))) + linphone_core_set_firewall_policy(linphone_gtk_get_core(),LinphonePolicyUseUpnp); +} + void linphone_gtk_mtu_changed(GtkWidget *w){ if (GTK_WIDGET_SENSITIVE(w)) linphone_core_set_mtu(linphone_gtk_get_core(),gtk_spin_button_get_value(GTK_SPIN_BUTTON(w))); @@ -1074,6 +1079,9 @@ void linphone_gtk_show_parameters(void){ case LinphonePolicyUseIce: gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(linphone_gtk_get_widget(pb,"use_ice")),TRUE); break; + case LinphonePolicyUseUpnp: + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(linphone_gtk_get_widget(pb,"use_upnp")),TRUE); + break; } mtu=linphone_core_get_mtu(lc); if (mtu<=0){ diff --git a/mediastreamer2 b/mediastreamer2 index 3a231efb8..2e0ef7e31 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 3a231efb89305d775ab39a6866ee981a891aed7e +Subproject commit 2e0ef7e31e919b524cfbe1ff4ffbc409fce3599c