Browse Source

modules/mediaproxy: Added support for ICE negotiation.

Patch provided by Sal Ibarra Corretg.
Juha Heinanen 15 years ago
parent
commit
1dd0a33bd1
3 changed files with 541 additions and 206 deletions
  1. 232 170
      modules/mediaproxy/README
  2. 55 0
      modules/mediaproxy/doc/mediaproxy_admin.xml
  3. 254 36
      modules/mediaproxy/mediaproxy.c

+ 232 - 170
modules/mediaproxy/README

@@ -12,35 +12,36 @@ Dan Pascu
 
    Copyright © 2004 Dan Pascu
    Revision History
-   Revision $Revision$ $Date: 2008-05-30 17:58:10 +0200
-                              (Fr, 30 Mai 2008) $
-     __________________________________________________________
+   Revision $Revision$ $Date$
+     __________________________________________________________________
 
    Table of Contents
 
    1. Admin Guide
 
-        1.1. Overview
-        1.2. Principle of operation
-        1.3. Features
-        1.4. Dependencies
+        1. Overview
+        2. Principle of operation
+        3. Features
+        4. Dependencies
 
-              1.4.1. Kamailio Modules
-              1.4.2. External Libraries or Applications
+              4.1. Kamailio Modules
+              4.2. External Libraries or Applications
 
-        1.5. Exported parameters
+        5. Exported parameters
 
-              1.5.1. disable (int)
-              1.5.2. mediaproxy_socket (string)
-              1.5.3. mediaproxy_timeout (int)
-              1.5.4. signaling_ip_avp (string)
-              1.5.5. media_relay_avp (string)
+              5.1. disable (int)
+              5.2. mediaproxy_socket (string)
+              5.3. mediaproxy_timeout (int)
+              5.4. signaling_ip_avp (string)
+              5.5. media_relay_avp (string)
+              5.6. ice_candidate (string)
+              5.7. ice_candidate_avp (string)
 
-        1.6. Exported Functions
+        6. Exported Functions
 
-              1.6.1. engage_media_proxy()
-              1.6.2. use_media_proxy()
-              1.6.3. end_media_session()
+              6.1. engage_media_proxy()
+              6.2. use_media_proxy()
+              6.3. end_media_session()
 
    List of Examples
 
@@ -49,102 +50,134 @@ Dan Pascu
    1.3. Setting the mediaproxy_timeout parameter
    1.4. Setting the signaling_ip_avp parameter
    1.5. Setting the media_relay_avp parameter
-   1.6. Using the engage_media_proxy function
-   1.7. Using the use_media_proxy function
-   1.8. Using the end_media_session function
+   1.6. Setting the ice_candidate parameter
+   1.7. Setting the ice_candidate_avp parameter
+   1.8. Using the engage_media_proxy function
+   1.9. Using the use_media_proxy function
+   1.10. Using the end_media_session function
 
 Chapter 1. Admin Guide
 
-1.1. Overview
+   Table of Contents
+
+   1. Overview
+   2. Principle of operation
+   3. Features
+   4. Dependencies
+
+        4.1. Kamailio Modules
+        4.2. External Libraries or Applications
+
+   5. Exported parameters
+
+        5.1. disable (int)
+        5.2. mediaproxy_socket (string)
+        5.3. mediaproxy_timeout (int)
+        5.4. signaling_ip_avp (string)
+        5.5. media_relay_avp (string)
+        5.6. ice_candidate (string)
+        5.7. ice_candidate_avp (string)
+
+   6. Exported Functions
 
-   Mediaproxy is an Kamailio module that is designed to allow
-   automatic NAT traversal for the majority of existing SIP
-   clients. This means that there will be no need to configure
-   anything in particular on the NAT box to allow these clients to
-   work behind NAT when using the mediaproxy module.
+        6.1. engage_media_proxy()
+        6.2. use_media_proxy()
+        6.3. end_media_session()
 
-1.2. Principle of operation
+1. Overview
 
-   This NAT traversal solution operates by placing a media relay
-   in the middle between 2 SIP user-agents. It mangles the SDP
-   messages for both of them in a way that will make the parties
-   talk with the relay while they think they talk directly with
-   each other.
+   Mediaproxy is an Kamailio module that is designed to allow automatic
+   NAT traversal for the majority of existing SIP clients. This means that
+   there will be no need to configure anything in particular on the NAT
+   box to allow these clients to work behind NAT when using the mediaproxy
+   module.
+
+2. Principle of operation
+
+   This NAT traversal solution operates by placing a media relay in the
+   middle between 2 SIP user-agents. It mangles the SDP messages for both
+   of them in a way that will make the parties talk with the relay while
+   they think they talk directly with each other.
 
    Mediaproxy consists of 2 components:
      * The Kamailio mediaproxy module
      * An external application called MediaProxy which employs a
-       dispatcher and multiple distributed media relays. This is
-       available from http://ag-projects.com/MediaProxy.html
-       (version 2.0.0 or newer is required by this module).
-
-   The mediaproxy dispatcher runs on the same machine as Kamailio
-   and its purpose is to select a media relay for a call. The
-   media relay may run on the same machine as the dispatcher or on
-   multiple remote hosts and its purpose is to forward the streams
-   between the calling parties. To find out more about the
-   architecture of MediaProxy please read the documentation that
-   comes with it.
-
-   To be able to act as a relay between the 2 user agents, the
-   machine(s) running the module/proxy server must have a public
-   IP address.
-
-   Kamailio will ask the media relay to allocate as many ports as
-   there are media streams in the SDP offer and answer. The media
-   relay will send back to Kamailio the IP address and port(s) for
-   them. Then Kamailio will replace the original contact IP and
-   RTP ports from the SDP messages with the ones provided by the
-   media relay. By doing this, both user agents will try to
-   contact the media relay instead of communicating directly with
-   each other. Once the user agents contact the media relay, it
-   will record the addresses they came from and will know where to
-   forward packets received from the other endpoint. This is
-   needed because the address/port the NAT box will allocate for
-   the media streams is not known before they actually leave the
-   NAT box. However the address of the media relay is always known
-   (being a public IP) so the 2 endpoints know where to connect.
-   After they do so, the relay learns their addresses and can
-   forward packets between them.
-
-   The SIP clients that will work transparently behind NAT when
-   using mediaproxy, are the so-called symmetric clients. The
-   symmetric clients have the particularity that use the same port
-   to send and receive data. This must be true for both signaling
-   and media for a client to work transparently with mediaproxy
-   without any configuration on the NAT box.
-
-1.3. Features
-
-     * make symmetric clients work behind NAT transparently, with
-       no configuration needed on the client's NAT box.
-     * have the ability to distribute RTP traffic on multiple
-       media relays running on multiple hosts.
-
-1.4. Dependencies
-
-1.4.1. Kamailio Modules
+       dispatcher and multiple distributed media relays. This is available
+       from http://ag-projects.com/MediaProxy.html (version 2.0.0 or newer
+       is required by this module).
+
+   The mediaproxy dispatcher runs on the same machine as Kamailio and its
+   purpose is to select a media relay for a call. The media relay may run
+   on the same machine as the dispatcher or on multiple remote hosts and
+   its purpose is to forward the streams between the calling parties. To
+   find out more about the architecture of MediaProxy please read the
+   documentation that comes with it.
+
+   To be able to act as a relay between the 2 user agents, the machine(s)
+   running the module/proxy server must have a public IP address.
+
+   Kamailio will ask the media relay to allocate as many ports as there
+   are media streams in the SDP offer and answer. The media relay will
+   send back to Kamailio the IP address and port(s) for them. Then
+   Kamailio will replace the original contact IP and RTP ports from the
+   SDP messages with the ones provided by the media relay. By doing this,
+   both user agents will try to contact the media relay instead of
+   communicating directly with each other. Once the user agents contact
+   the media relay, it will record the addresses they came from and will
+   know where to forward packets received from the other endpoint. This is
+   needed because the address/port the NAT box will allocate for the media
+   streams is not known before they actually leave the NAT box. However
+   the address of the media relay is always known (being a public IP) so
+   the 2 endpoints know where to connect. After they do so, the relay
+   learns their addresses and can forward packets between them.
+
+   The SIP clients that will work transparently behind NAT when using
+   mediaproxy, are the so-called symmetric clients. The symmetric clients
+   have the particularity that use the same port to send and receive data.
+   This must be true for both signaling and media for a client to work
+   transparently with mediaproxy without any configuration on the NAT box.
+
+3. Features
+
+     * make symmetric clients work behind NAT transparently, with no
+       configuration needed on the client's NAT box.
+     * have the ability to distribute RTP traffic on multiple media relays
+       running on multiple hosts.
+
+4. Dependencies
+
+   4.1. Kamailio Modules
+   4.2. External Libraries or Applications
+
+4.1. Kamailio Modules
 
    The following modules must be loaded before this module:
-     * dialog module - if engage_media_proxy is used (see below
-       the description of engage_media_proxy).
+     * dialog module - if engage_media_proxy is used (see below the
+       description of engage_media_proxy).
 
-1.4.2. External Libraries or Applications
+4.2. External Libraries or Applications
 
-   The following libraries or applications must be installed
-   before running Kamailio with this module loaded:
+   The following libraries or applications must be installed before
+   running Kamailio with this module loaded:
      * None.
 
-1.5. Exported parameters
+5. Exported parameters
+
+   5.1. disable (int)
+   5.2. mediaproxy_socket (string)
+   5.3. mediaproxy_timeout (int)
+   5.4. signaling_ip_avp (string)
+   5.5. media_relay_avp (string)
+   5.6. ice_candidate (string)
+   5.7. ice_candidate_avp (string)
 
-1.5.1. disable (int)
+5.1. disable (int)
 
-   Boolean flag that specifies if mediaproxy should be disabled.
-   This is useful when you want to use the same openser
-   configuration in two different context, one using mediaproxy,
-   the other not. In the case mediaproxy is disabled, calls to its
-   functions will have no effect, allowing you to use the same
-   configuration without changes.
+   Boolean flag that specifies if mediaproxy should be disabled. This is
+   useful when you want to use the same openser configuration in two
+   different context, one using mediaproxy, the other not. In the case
+   mediaproxy is disabled, calls to its functions will have no effect,
+   allowing you to use the same configuration without changes.
 
    Default value is "0".
 
@@ -153,20 +186,20 @@ Chapter 1. Admin Guide
 modparam("mediaproxy", "disable", 1)
 ...
 
-1.5.2. mediaproxy_socket (string)
+5.2. mediaproxy_socket (string)
 
-   It is the path to the filesystem socket where the mediaproxy
-   dispatcher listens for commands from the module.
+   It is the path to the filesystem socket where the mediaproxy dispatcher
+   listens for commands from the module.
 
    Default value is "/var/run/mediaproxy/dispatcher.sock".
 
    Example 1.2. Setting the mediaproxy_socket parameter
 ...
-modparam("mediaproxy", "mediaproxy_socket", "/var/run/mediaproxy/dispatc
-her.sock")
+modparam("mediaproxy", "mediaproxy_socket", "/var/run/mediaproxy/dispatcher.sock
+")
 ...
 
-1.5.3. mediaproxy_timeout (int)
+5.3. mediaproxy_timeout (int)
 
    How much time (in milliseconds) to wait for an answer from the
    mediaproxy dispatcher.
@@ -178,21 +211,20 @@ her.sock")
 modparam("mediaproxy", "mediaproxy_timeout", 500)
 ...
 
-1.5.4. signaling_ip_avp (string)
-
-   Specification of the AVP which holds the IP address from where
-   the SIP signaling originated. If this AVP is set it will be
-   used to get the signaling IP address, else the source IP
-   address from where the SIP message was received will be used.
-   This AVP is meant to be used in cases where there are more than
-   one proxy in the call setup path and the proxy that actually
-   starts mediaproxy doesn't receive the SIP messages directly
-   from the UA and it cannot determine the NAT IP address from
-   where the signaling originated. In such a case attaching a SIP
-   header at the first proxy and then copying that header's value
-   into the signaling_ip_avp on the proxy that starts mediaproxy
-   will allow it to get the correct NAT IP address from where the
-   SIP signaling originated.
+5.4. signaling_ip_avp (string)
+
+   Specification of the AVP which holds the IP address from where the SIP
+   signaling originated. If this AVP is set it will be used to get the
+   signaling IP address, else the source IP address from where the SIP
+   message was received will be used. This AVP is meant to be used in
+   cases where there are more than one proxy in the call setup path and
+   the proxy that actually starts mediaproxy doesn't receive the SIP
+   messages directly from the UA and it cannot determine the NAT IP
+   address from where the signaling originated. In such a case attaching a
+   SIP header at the first proxy and then copying that header's value into
+   the signaling_ip_avp on the proxy that starts mediaproxy will allow it
+   to get the correct NAT IP address from where the SIP signaling
+   originated.
 
    Default value is "$avp(s:signaling_ip)".
 
@@ -201,14 +233,13 @@ modparam("mediaproxy", "mediaproxy_timeout", 500)
 modparam("mediaproxy", "signaling_ip_avp", "$avp(s:nat_ip)")
 ...
 
-1.5.5. media_relay_avp (string)
+5.5. media_relay_avp (string)
 
-   Specification of the AVP which holds an optional application
-   defined media relay IP address of a particular media relay that
-   is preferred to be used for the current call. If an IP address
-   is written to this AVP before calling use_media_proxy(), it
-   will be preferred by the dispatcher over the normal selection
-   algorithm.
+   Specification of the AVP which holds an optional application defined
+   media relay IP address of a particular media relay that is preferred to
+   be used for the current call. If an IP address is written to this AVP
+   before calling use_media_proxy(), it will be preferred by the
+   dispatcher over the normal selection algorithm.
 
    Default value is "$avp(s:media_relay)".
 
@@ -217,38 +248,71 @@ modparam("mediaproxy", "signaling_ip_avp", "$avp(s:nat_ip)")
 modparam("mediaproxy", "media_relay_avp", "$avp(s:media_relay)")
 ...
 
-1.6. Exported Functions
-
-1.6.1. engage_media_proxy()
-
-   Trigger the use of MediaProxy for all the dialog requests and
-   replies that have an SDP body. This needs to be called only
-   once for the first INVITE in a dialog. After that it will use
-   the dialog module to trace the dialog and automatically call
-   use_media_proxy() on every request and reply that belongs to
-   the dialog and has an SDP body. When the dialog ends it will
-   also call automatically end_media_session(). All of these are
-   called internally on dialog callbacks, so for this function to
-   work, the dialog module must be loaded and configured.
-
-   This function is an advanced mechanism to use a media relay
-   without having to manually call a function on each message that
-   belongs to the dialog. However this method is less flexible,
-   because once things were set in motion by calling this function
-   on the first INVITE, it cannot be stopped, not even by calling
-   end_media_session(). It will only stop when the dialog ends.
-   Until then it will modify the SDP content of every in-dialog
-   message to make it use a media relay. If one needs more control
-   over the process, like starting to use mediaproxy only later in
-   the failure route, or stopping to use mediaproxy in the failure
-   route, then the use_media_proxy and end_media_session functions
-   should be used, and manually called as appropriate. Using this
-   function should NOT be mixed with either of use_media_proxy()
-   or end_media_session().
+5.6. ice_candidate (string)
+
+   Indicates the type of ICE candidate that will be added to the SDP. It
+   can take 3 values: 'none', 'low-priority' or 'high-priority'. If 'none'
+   is selected no candidate will be adeed to the SDP. If 'low-priority' is
+   selected then a low priority candidate will be added and if
+   'high-priority' is selected a high priority one.
+
+   Default value is "none".
+
+   Example 1.6. Setting the ice_candidate parameter
+...
+modparam("mediaproxy", "ice_candidate", "low-priority")
+...
+
+5.7. ice_candidate_avp (string)
+
+   Specification of the AVP which holds the ICE candidate that will be
+   inserted in the SDP. The value specified in this AVP will override the
+   value in ice_candidate module parameter. Note that if use_media_proxy()
+   and end_media_session() functions are being used, the AVP will not be
+   available in the reply route unless you set onreply_avp_mode from the
+   tm module to '1', and if the AVP is not set, the default value will be
+   used.
+
+   Default value is "$avp(s:ice_candidate)".
+
+   Example 1.7. Setting the ice_candidate_avp parameter
+...
+modparam("mediaproxy", "ice_candidate_avp", "$avp(s:ice_candidate)")
+...
+
+6. Exported Functions
+
+   6.1. engage_media_proxy()
+   6.2. use_media_proxy()
+   6.3. end_media_session()
+
+6.1. engage_media_proxy()
+
+   Trigger the use of MediaProxy for all the dialog requests and replies
+   that have an SDP body. This needs to be called only once for the first
+   INVITE in a dialog. After that it will use the dialog module to trace
+   the dialog and automatically call use_media_proxy() on every request
+   and reply that belongs to the dialog and has an SDP body. When the
+   dialog ends it will also call automatically end_media_session(). All of
+   these are called internally on dialog callbacks, so for this function
+   to work, the dialog module must be loaded and configured.
+
+   This function is an advanced mechanism to use a media relay without
+   having to manually call a function on each message that belongs to the
+   dialog. However this method is less flexible, because once things were
+   set in motion by calling this function on the first INVITE, it cannot
+   be stopped, not even by calling end_media_session(). It will only stop
+   when the dialog ends. Until then it will modify the SDP content of
+   every in-dialog message to make it use a media relay. If one needs more
+   control over the process, like starting to use mediaproxy only later in
+   the failure route, or stopping to use mediaproxy in the failure route,
+   then the use_media_proxy and end_media_session functions should be
+   used, and manually called as appropriate. Using this function should
+   NOT be mixed with either of use_media_proxy() or end_media_session().
 
    This function can be used from REQUEST_ROUTE.
 
-   Example 1.6. Using the engage_media_proxy function
+   Example 1.8. Using the engage_media_proxy function
 ...
 if (method==INVITE && !has_totag()) {
     # We can also use a specific media relay if we need to
@@ -257,15 +321,14 @@ if (method==INVITE && !has_totag()) {
 }
 ...
 
-1.6.2. use_media_proxy()
+6.2. use_media_proxy()
 
-   Will make a call to the dispatcher and replace the IPs and
-   ports in the SDP body with the ones returned by the media relay
-   for each supported media stream in the SDP body. This will
-   force the media streams to be routed through the media relay.
-   If a mix of supported and unsupported streams are present in
-   the SDP, only the supported streams will be modified, while the
-   unsupported streams will be left alone.
+   Will make a call to the dispatcher and replace the IPs and ports in the
+   SDP body with the ones returned by the media relay for each supported
+   media stream in the SDP body. This will force the media streams to be
+   routed through the media relay. If a mix of supported and unsupported
+   streams are present in the SDP, only the supported streams will be
+   modified, while the unsupported streams will be left alone.
 
    This function should NOT be mixed with engage_media_proxy().
 
@@ -278,7 +341,7 @@ if (method==INVITE && !has_totag()) {
    This function can be used from REQUEST_ROUTE, ONREPLY_ROUTE,
    FAILURE_ROUTE, BRANCH_ROUTE.
 
-   Example 1.7. Using the use_media_proxy function
+   Example 1.9. Using the use_media_proxy function
 ...
 if (method==INVITE) {
     # We can also use a specific media relay if we need to
@@ -287,20 +350,19 @@ if (method==INVITE) {
 }
 ...
 
-1.6.3. end_media_session()
+6.3. end_media_session()
 
-   Will call on the dispatcher to inform the media relay to end
-   the media session. This is done when a call ends, to instruct
-   the media relay to release the resources allocated to that call
-   as well as to save logging information about the media session.
-   Called on BYE, CANCEL or failures.
+   Will call on the dispatcher to inform the media relay to end the media
+   session. This is done when a call ends, to instruct the media relay to
+   release the resources allocated to that call as well as to save logging
+   information about the media session. Called on BYE, CANCEL or failures.
 
    This function should NOT be mixed with engage_media_proxy().
 
    This function can be used from REQUEST_ROUTE, ONREPLY_ROUTE,
    FAILURE_ROUTE, BRANCH_ROUTE.
 
-   Example 1.8. Using the end_media_session function
+   Example 1.10. Using the end_media_session function
 ...
 if (method==BYE) {
     end_media_session();

+ 55 - 0
modules/mediaproxy/doc/mediaproxy_admin.xml

@@ -276,6 +276,61 @@ modparam("mediaproxy", "signaling_ip_avp", "$avp(s:nat_ip)")
         <programlisting format="linespecific">
 ...
 modparam("mediaproxy", "media_relay_avp", "$avp(s:media_relay)")
+...
+        </programlisting>
+      </example>
+    </section>
+    
+    <section>
+    <title><varname>ice_candidate</varname> (string)</title>
+      <para>
+        Indicates the type of ICE candidate that will be added to the SDP. 
+        It can take 3 values: 'none', 'low-priority' or 'high-priority'. 
+        If 'none' is selected no candidate will be adeed to the SDP. If 
+        'low-priority' is selected then a low priority candidate will be 
+        added and if 'high-priority' is selected a high priority one.
+      </para>
+
+      <para>
+        <emphasis>
+          Default value is <quote>none</quote>.
+        </emphasis>
+      </para>
+
+      <example>
+      <title>Setting the <varname>ice_candidate</varname> parameter</title>
+        <programlisting format="linespecific">
+...
+modparam("mediaproxy", "ice_candidate", "low-priority")
+...
+        </programlisting>
+      </example>
+    </section>
+    
+    <section>
+    <title><varname>ice_candidate_avp</varname> (string)</title>
+      <para>
+        Specification of the AVP which holds the ICE candidate that will be 
+        inserted in the SDP. The value specified in this AVP will override 
+        the value in ice_candidate module parameter.
+
+        Note that if use_media_proxy() and end_media_session() functions are
+        being used, the AVP will not be available in the reply route unless 
+        you set onreply_avp_mode from the tm module to '1', and if the AVP 
+        is not set, the default value will be used.
+      </para>
+
+      <para>
+        <emphasis>
+          Default value is <quote>$avp(s:ice_candidate)</quote>.
+        </emphasis>
+      </para>
+
+      <example>
+      <title>Setting the <varname>ice_candidate_avp</varname> parameter</title>
+        <programlisting format="linespecific">
+...
+modparam("mediaproxy", "ice_candidate_avp", "$avp(s:ice_candidate)")
 ...
         </programlisting>
       </example>

+ 254 - 36
modules/mediaproxy/mediaproxy.c

@@ -28,6 +28,7 @@
 #include <time.h>
 #include <ctype.h>
 #include <errno.h>
+#include <arpa/inet.h>
 #include <sys/time.h>
 #include <sys/types.h>
 #include <sys/socket.h>
@@ -65,7 +66,9 @@ MODULE_VERSION
 
 #define SIGNALING_IP_AVP_SPEC  "$avp(s:signaling_ip)"
 #define MEDIA_RELAY_AVP_SPEC   "$avp(s:media_relay)"
+#define ICE_CANDIDATE_AVP_SPEC "$avp(s:ice_candidate)"
 
+#define NO_CANDIDATE -1
 
 // Although `AF_LOCAL' is mandated by POSIX.1g, `AF_UNIX' is portable to
 // more systems.  `AF_UNIX' was the traditional name stemming from BSD, so
@@ -132,9 +135,11 @@ typedef struct {
     str rtcp_port; // pointer to the rtcp port if explicitly specified by stream
     str direction;
     Bool local_ip; // true if the IP is locally defined inside this media stream
+    Bool has_ice;
     TransportType transport;
     char *start_line;
     char *next_line;
+    char *first_ice_candidate;
 } StreamInfo;
 
 #define MAX_STREAMS 32
@@ -154,6 +159,10 @@ typedef struct AVP_Param {
     unsigned short type;
 } AVP_Param;
 
+typedef struct ice_candidate_data {
+    unsigned int priority;
+    Bool skip_next_reply;
+} ice_candidate_data;
 
 // Function prototypes
 //
@@ -168,6 +177,7 @@ static int child_init(int rank);
 // Module global variables and state
 //
 static int mediaproxy_disabled = False;
+static str ice_candidate = str_init("none");
 
 static MediaproxySocket mediaproxy_socket = {
     "/var/run/mediaproxy/dispatcher.sock", // name
@@ -188,6 +198,9 @@ static AVP_Param signaling_ip_avp = {str_init(SIGNALING_IP_AVP_SPEC), {0}, 0};
 // The AVP where the application-defined media relay IP is stored
 static AVP_Param media_relay_avp = {str_init(MEDIA_RELAY_AVP_SPEC), {0}, 0};
 
+// The AVP where the ICE candidate priority is stored (if defined)
+static AVP_Param ice_candidate_avp = {str_init(ICE_CANDIDATE_AVP_SPEC), {0}, 0};
+
 static cmd_export_t commands[] = {
     {"engage_media_proxy", (cmd_function)EngageMediaProxy, 0, 0, 0, REQUEST_ROUTE},
     {"use_media_proxy",    (cmd_function)UseMediaProxy,    0, 0, 0, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE | BRANCH_ROUTE | LOCAL_ROUTE},
@@ -201,6 +214,8 @@ static param_export_t parameters[] = {
     {"mediaproxy_timeout", INT_PARAM, &(mediaproxy_socket.timeout)},
     {"signaling_ip_avp",   STR_PARAM, &(signaling_ip_avp.spec.s)},
     {"media_relay_avp",    STR_PARAM, &(media_relay_avp.spec.s)},
+    {"ice_candidate",      STR_PARAM, &(ice_candidate.s)},
+    {"ice_candidate_avp",  STR_PARAM, &(ice_candidate_avp.spec.s)},
     {0, 0, 0}
 };
 
@@ -901,6 +916,64 @@ get_rtcp_ip_attribute(str *block)
 }
 
 
+// will return true if the stream in the given block
+// has ice proposal/answer, false otherwise
+static Bool
+has_ice_proposal(str *block)
+{
+    char *ptr;
+    ptr = find_line_starting_with(block, "a=ice-pwd:", False);
+    if (ptr) {
+        ptr = find_line_starting_with(block, "a=ice-ufrag:", False);
+        if (ptr) {
+            ptr = find_line_starting_with(block, "a=candidate:", False);
+            if (ptr) {
+                return True;
+            }
+        }
+    }
+    return False;
+}
+
+
+// will return the priority (string value) that will be used
+// for the candidate(s) inserted
+static str
+get_ice_candidate(void)
+{
+    int_str value;
+
+    if (!search_first_avp(ice_candidate_avp.type | AVP_VAL_STR,
+                          ice_candidate_avp.name, &value, NULL) || value.s.s==NULL || value.s.len==0) {
+        // if AVP is not set use global module parameter
+        return ice_candidate;
+    } else {
+        return value.s;
+    }
+}
+
+
+// will return the priority (integer value) that will be used
+// for the candidate(s) inserted
+static unsigned int
+get_ice_candidate_priority(str priority)
+{
+    int type_pref;
+
+    if (STR_IMATCH(priority, "high-priority")) {
+        // Use type preference even higher than host candidates
+        type_pref = 130;
+    } else if (STR_IMATCH(priority, "low-priority")) {
+        type_pref = 0;
+    } else {
+        return NO_CANDIDATE;
+    }
+    // This will return the priority for the RTP component, the RTCP
+    // component is RTP - 1
+    return ((type_pref << 24) + 16777215);
+}
+
+
 // will return the ip address present in a `c=' line in the given block
 // returns: -1 on error, 0 if not found, 1 if found
 static int
@@ -997,6 +1070,27 @@ get_session_direction(str *sdp)
 }
 
 
+// will return the method ID for a reply by inspecting the Cseq header
+static int
+get_method_from_reply(struct sip_msg *reply)
+{
+    struct cseq_body *cseq;
+
+    if (reply->first_line.type != SIP_REPLY)
+        return -1;
+
+    if (!reply->cseq && parse_headers(reply, HDR_CSEQ_F, 0) < 0) {
+        LM_ERR("failed to parse the CSeq header\n");
+        return -1;
+    }
+    if (!reply->cseq) {
+        LM_ERR("missing CSeq header\n");
+        return -1;
+    }
+    cseq = reply->cseq->parsed;
+    return cseq->method_id;
+}
+
 static Bool
 supported_transport(str transport)
 {
@@ -1150,6 +1244,8 @@ get_session_info(str *sdp, SessionInfo *session)
         session->streams[i].rtcp_ip = get_rtcp_ip_attribute(&block);
         session->streams[i].rtcp_port = get_rtcp_port_attribute(&block);
         session->streams[i].direction = get_direction_attribute(&block, &session->direction);
+        session->streams[i].has_ice = has_ice_proposal(&block);
+        session->streams[i].first_ice_candidate = find_line_starting_with(&block, "a=candidate:", False);
     }
 
     return session->stream_count;
@@ -1387,14 +1483,21 @@ send_command(char *command)
 // Exported API implementation
 //
 
+// ice_candidate_data: it carries data across the dialog when using engage_media_proxy:
+//   - priority: the priority that should be used for the ICE candidate
+//      * -1: no candidate should be added.
+//      * other: the specified type preference should be used for calculating 
+//   - skip_next_reply: flag for knowing the fact that the next reply with SDP must be skipped
+//     because it is a reply to a re-INVITE or UPDATE *after* the ICE negotiation
 static int
-use_media_proxy(struct sip_msg *msg, char *dialog_id)
+use_media_proxy(struct sip_msg *msg, char *dialog_id, ice_candidate_data *ice_data)
 {
     str callid, cseq, from_uri, to_uri, from_tag, to_tag, user_agent;
     str signaling_ip, media_relay, sdp, str_buf, tokens[MAX_STREAMS+1];
+    str priority_str, candidate;
     char request[8192], media_str[4096], buf[64], *result, *type;
     int i, j, port, len, status;
-    Bool removed_session_ip;
+    Bool removed_session_ip, have_sdp;
     SessionInfo session;
     StreamInfo stream;
 
@@ -1404,6 +1507,12 @@ use_media_proxy(struct sip_msg *msg, char *dialog_id)
     if (msg->first_line.type == SIP_REQUEST) {
         type = "request";
     } else if (msg->first_line.type == SIP_REPLY) {
+        if (ice_data != NULL && ice_data->skip_next_reply) {
+            // we don't process replies to ICE negotiation end requests 
+            // (those containing a=remote-candidates)
+            ice_data->skip_next_reply = False;
+            return -1;
+        }
         type = "reply";
     } else {
         return -1;
@@ -1421,37 +1530,55 @@ use_media_proxy(struct sip_msg *msg, char *dialog_id)
 
     status = get_sdp_message(msg, &sdp);
     // status = -1 is error, -2 is missing SDP body
-    if (status < 0)
+    if (status == -1 || (status == -2 && msg->first_line.type == SIP_REQUEST)) {
         return status;
-
-    status = get_session_info(&sdp, &session);
-    if (status < 0) {
-        LM_ERR("can't extract media streams from the SDP message\n");
-        return -1;
+    } else if (status == -2 && !(msg->REPLY_STATUS == 200 && get_method_from_reply(msg) == METHOD_INVITE)) {
+        return -2;
     }
+    have_sdp = (status == 1);
 
-    if (session.supported_count == 0)
-        return 1; // there are no supported media streams. we have nothing to do.
-
-    for (i=0, str_buf.len=sizeof(media_str), str_buf.s=media_str; i<session.stream_count; i++) {
-        stream = session.streams[i];
-        if (stream.transport != TSupported)
-            continue; // skip streams with unsupported transports
-        if (stream.type.len + stream.ip.len + stream.port.len + stream.direction.len + 4 > str_buf.len) {
-            LM_ERR("media stream description is longer than %lu bytes\n",
-				(unsigned long)sizeof(media_str));
+    if (have_sdp) {
+        if (msg->first_line.type == SIP_REQUEST && find_line_starting_with(&sdp, "a=remote-candidates", False)) {
+            // we don't process requests with a=remote-candidates, this indicates the end of an ICE
+            // negotiation and we must not mangle the SDP.
+            if (ice_data != NULL) {
+                ice_data->skip_next_reply = True;
+            }
             return -1;
         }
-        len = sprintf(str_buf.s, "%.*s:%.*s:%.*s:%.*s,",
-                      stream.type.len, stream.type.s,
-                      stream.ip.len, stream.ip.s,
-                      stream.port.len, stream.port.s,
-                      stream.direction.len, stream.direction.s);
-        str_buf.s   += len;
-        str_buf.len -= len;
-    }
+       
+        status = get_session_info(&sdp, &session);
+        if (status < 0) {
+            LM_ERR("can't extract media streams from the SDP message\n");
+            return -1;
+        }
+
+        if (session.supported_count == 0)
+            return 1; // there are no supported media streams. we have nothing to do.
 
-    *(str_buf.s-1) = 0; // remove the last comma
+        len = sprintf(media_str, "%s", "media: ");
+        for (i=0, str_buf.len=sizeof(media_str)-len-2, str_buf.s=media_str+len; i<session.stream_count; i++) {
+            stream = session.streams[i];
+            if (stream.transport != TSupported)
+                continue; // skip streams with unsupported transports
+            if (stream.type.len + stream.ip.len + stream.port.len + stream.direction.len + 4 > str_buf.len) {
+                LM_ERR("media stream description is longer than %lu bytes\n", (unsigned long)sizeof(media_str));
+                return -1;
+            }
+            len = sprintf(str_buf.s, "%.*s:%.*s:%.*s:%.*s:%s,",
+                          stream.type.len, stream.type.s,
+                          stream.ip.len, stream.ip.s,
+                          stream.port.len, stream.port.s,
+                          stream.direction.len, stream.direction.s,
+                          stream.has_ice?"ice=yes":"ice=no");
+            str_buf.s   += len;
+            str_buf.len -= len;
+        }
+        *(str_buf.s-1) = 0; // remove the last comma
+        sprintf(str_buf.s-1, "%s", "\r\n");
+    } else {
+        media_str[0] = 0;
+    }
 
     from_uri     = get_from_uri(msg);
     to_uri       = get_to_uri(msg);
@@ -1472,16 +1599,16 @@ use_media_proxy(struct sip_msg *msg, char *dialog_id)
                    "from_tag: %.*s\r\n"
                    "to_tag: %.*s\r\n"
                    "user_agent: %.*s\r\n"
-                   "media: %s\r\n"
                    "signaling_ip: %.*s\r\n"
                    "media_relay: %.*s\r\n"
+                   "%s"
                    "\r\n",
                    type, dialog_id, callid.len, callid.s, cseq.len, cseq.s,
                    from_uri.len, from_uri.s, to_uri.len, to_uri.s,
                    from_tag.len, from_tag.s, to_tag.len, to_tag.s,
-                   user_agent.len, user_agent.s, media_str,
+                   user_agent.len, user_agent.s,
                    signaling_ip.len, signaling_ip.s,
-                   media_relay.len, media_relay.s);
+                   media_relay.len, media_relay.s, media_str);
 
     if (len >= sizeof(request)) {
         LM_ERR("mediaproxy request is longer than %lu bytes\n",
@@ -1494,6 +1621,12 @@ use_media_proxy(struct sip_msg *msg, char *dialog_id)
     if (result == NULL)
         return -1;
 
+    if (!have_sdp) {
+        // we updated the dispatcher, we can't do anything else as
+        // there is no SDP
+        return 1;
+    }
+
     len = get_tokens(result, tokens, sizeof(tokens)/sizeof(str));
 
     if (len == 0) {
@@ -1588,6 +1721,52 @@ use_media_proxy(struct sip_msg *msg, char *dialog_id)
             }
         }
 
+        if (ice_data == NULL) {
+            priority_str = get_ice_candidate();
+        } else if (ice_data->priority == NO_CANDIDATE) {
+            priority_str.s = "none";
+        } else {
+            // we don't need the string value, we'll use the number
+            priority_str.s = "";
+        }
+        priority_str.len = strlen(priority_str.s);
+
+        if (stream.has_ice && stream.first_ice_candidate && !STR_IMATCH(priority_str, "none")) {
+            // add some pseudo-random string to the foundation
+            struct in_addr hexip;
+            inet_aton(tokens[0].s, &hexip);
+
+            unsigned int priority = (ice_data == NULL)?get_ice_candidate_priority(priority_str):ice_data->priority;
+            port = strtoint(&tokens[j]);
+            candidate.s = buf;
+            candidate.len = sprintf(candidate.s, "a=candidate:R%x 1 UDP %u %.*s %i typ relay%.*s",
+                                    hexip.s_addr,
+                                    priority,
+                                    tokens[0].len, tokens[0].s, 
+                                    port,
+                                    session.separator.len, session.separator.s);
+
+            if (!insert_element(msg, stream.first_ice_candidate, candidate.s)) {
+                LM_ERR("failed to insert ICE candidate in media stream number %d\n", i+1);
+                return -1;
+            }
+
+            if (stream.rtcp_port.len>0 && !isnullport(stream.rtcp_port)) {
+                candidate.s = buf;
+                candidate.len = sprintf(candidate.s, "a=candidate:R%x 2 UDP %u %.*s %i typ relay%.*s",
+                                        hexip.s_addr,
+                                        priority-1,
+                                        tokens[0].len, tokens[0].s, 
+                                        port+1,
+                                        session.separator.len, session.separator.s);
+
+                if (!insert_element(msg, stream.first_ice_candidate, candidate.s)) {
+                    LM_ERR("failed to insert ICE candidate in media stream number %d\n", i+1);
+                    return -1;
+                }
+            }
+        }
+
         j++;
     }
 
@@ -1643,10 +1822,17 @@ get_dialog_id(struct dlg_cell *dlg)
 }
 
 
+static void
+__free_dialog_data(void *data)
+{
+    shm_free((ice_candidate_data*)data);
+}
+
+
 static void
 __dialog_requests(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params)
 {
-    use_media_proxy(_params->msg, get_dialog_id(dlg));
+    use_media_proxy(_params->msg, get_dialog_id(dlg), (ice_candidate_data*)*_params->param);
 }
 
 
@@ -1659,7 +1845,7 @@ __dialog_replies(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params)
         return;
 
     if (reply->REPLY_STATUS>100 && reply->REPLY_STATUS<300) {
-        use_media_proxy(reply, get_dialog_id(dlg));
+        use_media_proxy(reply, get_dialog_id(dlg), (ice_candidate_data*)*_params->param);
     }
 }
 
@@ -1678,6 +1864,7 @@ static void
 __dialog_created(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params)
 {
     struct sip_msg *request = _params->msg;
+    ice_candidate_data *ice_data;
 
     if (request->REQ_METHOD != METHOD_INVITE)
         return;
@@ -1685,14 +1872,23 @@ __dialog_created(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params)
     if ((request->msg_flags & FL_USE_MEDIA_PROXY) == 0)
         return;
 
-    if (dlg_api.register_dlgcb(dlg, DLGCB_REQ_WITHIN, __dialog_requests, NULL, NULL) != 0)
+    ice_data = (ice_candidate_data*)shm_malloc(sizeof(ice_candidate_data));
+    if (!ice_data) {
+        LM_ERR("failed to allocate shm memory for ice_candidate_data\n");
+        return;
+    }
+
+    ice_data->priority = get_ice_candidate_priority(get_ice_candidate());
+    ice_data->skip_next_reply = False;
+
+    if (dlg_api.register_dlgcb(dlg, DLGCB_REQ_WITHIN, __dialog_requests, (void*)ice_data, __free_dialog_data) != 0)
         LM_ERR("cannot register callback for in-dialog requests\n");
-    if (dlg_api.register_dlgcb(dlg, DLGCB_RESPONSE_FWDED | DLGCB_RESPONSE_WITHIN, __dialog_replies, NULL, NULL) != 0)
+    if (dlg_api.register_dlgcb(dlg, DLGCB_RESPONSE_FWDED | DLGCB_RESPONSE_WITHIN, __dialog_replies, (void*)ice_data, NULL) != 0)
         LM_ERR("cannot register callback for dialog and in-dialog replies\n");
     if (dlg_api.register_dlgcb(dlg, DLGCB_TERMINATED | DLGCB_FAILED | DLGCB_EXPIRED | DLGCB_DESTROY, __dialog_ended, (void*)MPActive, NULL) != 0)
         LM_ERR("cannot register callback for dialog termination\n");
 
-    use_media_proxy(request, get_dialog_id(dlg));
+    use_media_proxy(request, get_dialog_id(dlg), ice_data);
 }
 
 
@@ -1723,7 +1919,7 @@ UseMediaProxy(struct sip_msg *msg)
     if (mediaproxy_disabled)
         return -1;
 
-    return use_media_proxy(msg, "");
+    return use_media_proxy(msg, "", NULL);
 }
 
 
@@ -1789,6 +1985,28 @@ mod_init(void)
         return -1;
     }
 
+    // initialize the ice_candidate_avp structure
+    if (ice_candidate_avp.spec.s==NULL || *(ice_candidate_avp.spec.s)==0) {
+        LM_WARN("missing/empty ice_candidate_avp parameter. will use default.\n");
+        ice_candidate_avp.spec.s = ICE_CANDIDATE_AVP_SPEC;
+    }
+    ice_candidate_avp.spec.len = strlen(ice_candidate_avp.spec.s);
+    if (pv_parse_spec(&(ice_candidate_avp.spec), &avp_spec)==0 || avp_spec.type!=PVT_AVP) {
+        LM_CRIT("invalid AVP specification for ice_candidate_avp: `%s'\n", ice_candidate_avp.spec.s);
+        return -1;
+    }
+    if (pv_get_avp_name(0, &(avp_spec.pvp), &(ice_candidate_avp.name), &(ice_candidate_avp.type))!=0) {
+        LM_CRIT("invalid AVP specification for ice_candidate_avp: `%s'\n", ice_candidate_avp.spec.s);
+        return -1;
+    }
+
+    // initialize ice_candidate module parameter
+    ice_candidate.len = strlen(ice_candidate.s);
+    if (!STR_IMATCH(ice_candidate, "none") && !STR_IMATCH(ice_candidate, "low-priority") && !STR_IMATCH(ice_candidate, "high-priority")) {
+        LM_CRIT("invalid value specified for ice_candidate: `%s'\n", ice_candidate.s);
+        return -1;
+    }
+
     // bind to the dialog API
     if (load_dlg_api(&dlg_api)==0) {
         have_dlg_api = True;