Jelajahi Sumber

Merge remote branch 'origin/andrei/cancel_reason'

CANCEL Reason support, according to RFC 3326.
The Reason headers are added both for CANCELs generated due to a
received final reply and hop by hop CANCELs generated because of
a received CANCEL. Both cases can be turned on/off individually.

* origin/andrei/cancel_reason:
  tm: no reason modparams if compiled with no cancelr. support
  tm: option to compile without reason support
  tm: docs: reason header modparams and script function
  tm: improved Reason support for E2E CANCELs
  tm: fix Reason generation for on-the-fly branch CANCELs
  tm: CANCEL reason header on/off switches
  tm: Reason header copy for received CANCELs
  tm: Reason header generation for local CANCELs
Andrei Pelinescu-Onciul 15 tahun lalu
induk
melakukan
c16a8388b1

+ 17 - 0
NEWS

@@ -109,6 +109,23 @@ modules:
             increase dramatically especially for large number of
             connections (>1000).
 tm:
+   - reason header support (RFC3326) both for CANCELs generated due to a
+      received final reply and for hop by hop CANCELs generated because of a
+      received CANCEL.
+      E.g.: reason header added for a CANCEL generated after a 200 reply was
+            received on one of the branches "Reason: SIP;cause=200".
+      The reason header support can be turned on/off using either tm
+      module parameters or in the end to end CANCEL case also on a per
+      transaction basis, using a script function:
+       local_cancel_reason = 0 | 1 (default 1/on) - turns on adding reason
+         headers for CANCELs generated due to a final reply. Can be changed
+         at runtime.
+       e2e_cancel_reason = 0 | 1 (default 1/on) - turns on copying reason
+         headers from a received end to end CANCEL (the generated hop by hop
+         CANCELs will have the same reason headers as the received CANCEL).
+         Can be changed at runtime.
+       t_set_no_e2e_cancel_reason(0|1) - enable/disable cancel reason 
+         header copying on a per transaction basis (0 - enable, 1 disable).
    - t_reply() can be used both from the main/core onreply_route{} and tm
      onreply_route[...]{}s.
 

+ 100 - 40
modules/tm/README

@@ -8,9 +8,9 @@ Juha Heinanen
 
    <[email protected]>
 
-   Copyright © 2003 FhG FOKUS
+   Copyright © 2003 FhG FOKUS
 
-   Copyright © 2008 Juha Heinanen
+   Copyright © 2008 Juha Heinanen
    Revision History
    Revision $Revision$ $Date$
      __________________________________________________________________
@@ -57,6 +57,8 @@ Juha Heinanen
         1.4.35. disable_6xx_block (integer)
         1.4.36. local_ack_mode (integer)
         1.4.37. failure_reply_mode (integer)
+        1.4.38. local_cancel_reason (boolean)
+        1.4.39. e2e_cancel_reason (boolean)
 
    1.5. Functions
 
@@ -105,6 +107,7 @@ Juha Heinanen
         1.5.35. t_set_disable_failover(0|1)
         1.5.36. t_replicate(params)
         1.5.37. t_relay_to(proxy, flags)
+        1.5.38. t_set_no_e2e_cancel_reason(0|1)
 
    1.6. TM Module API
 
@@ -174,7 +177,7 @@ Juha Heinanen
 Note
 
    Several Kamailio (OpenSER) TM module functionalities are now
-   implemented in the TMX module: “modules_k/tmx�. Check it to see if what
+   implemented in the TMX module: "modules_k/tmx". Check it to see if what
    you are looking for is there.
 
 1.2. Serial Forking Based on Q Value
@@ -1131,6 +1134,42 @@ modparam("tm", "local_ack_mode", 1)
 modparam("tm", "failure_reply_mode", 3)
 ...
 
+1.4.38. local_cancel_reason (boolean)
+
+   Enables/disables adding reason headers (RFC 3326) for CANCELs generated
+   due to receiving a final reply. The reason header added will look like:
+   "Reason: SIP;cause=<final_reply_code>".
+
+   Default value is 1 (enabled).
+
+   Can be set at runtime, e.g.:
+        $ sercmd cfg.set_now_int tm local_cancel_reason 0
+
+   See also: e2e_cancel_reason.
+
+   Example 38. Set local_cancel_reason parameter
+...
+modparam("tm", "local_cancel_reason", "0")
+...
+
+1.4.39. e2e_cancel_reason (boolean)
+
+   Enables/disables adding reason headers (RFC 3326) for CANCELs generated
+   due to a received CANCEL. If enabled the reason headers from received
+   CANCELs will be copied into the generated hop-by-hop CANCELs.
+
+   Default value is 1 (enabled).
+
+   Can be changed at runtime, e.g.:
+        $ sercmd cfg.set_now_int tm e2e_cancel_reason 0
+
+   See also: t_set_no_e2e_cancel_reason() and local_cancel_reason.
+
+   Example 39. Set e2e_cancel_reason parameter
+...
+modparam("tm", "e2e_cancel_reason", "0")
+...
+
 1.5. Functions
 
    Revision History
@@ -1156,7 +1195,7 @@ t_relay_to_sctp(ip, port) t_relay_to_sctp()
    derived from the message uri (using sip sepcific DNS lookups), but with
    the protocol corresponding to the function name.
 
-   Example 38. t_relay_to_udp usage
+   Example 40. t_relay_to_udp usage
 ...
 if (src_ip==10.0.0.0/8)
         t_relay_to_udp("1.2.3.4", "5060"); # sent to 1.2.3.4:5060 over udp
@@ -1183,7 +1222,7 @@ else
    Returns a negative value on failure--you may still want to send a
    negative reply upstream statelessly not to leave upstream UAC in lurch.
 
-   Example 39. t_relay usage
+   Example 41. t_relay usage
 ...
 if (!t_relay())
 {
@@ -1212,7 +1251,7 @@ if (!t_relay())
    Meaning of the parameters is as follows:
      * failure_route - Failure route block to be called.
 
-   Example 40. t_on_failure usage
+   Example 42. t_on_failure usage
 ...
 route {
     t_on_failure("1");
@@ -1238,7 +1277,7 @@ failure_route[1] {
    Meaning of the parameters is as follows:
      * onreply_route - Onreply route block to be called.
 
-   Example 41. t_on_reply usage
+   Example 43. t_on_reply usage
 ...
 loadmodule "/usr/local/lib/ser/modules/nathelper.so"
 ...
@@ -1270,7 +1309,7 @@ es');
    Meaning of the parameters is as follows:
      * branch_route - branch route block to be called.
 
-   Example 42. t_on_branch usage
+   Example 44. t_on_branch usage
 ...
 route {
         t_on_branch("1");
@@ -1288,7 +1327,7 @@ branch_route[1] {
    Similarly to t_fork_to, it extends destination set by a new entry. The
    difference is that current URI is taken as new entry.
 
-   Example 43. append_branch usage
+   Example 45. append_branch usage
 ...
 set_user("john");
 t_fork();
@@ -1303,7 +1342,7 @@ t_relay();
    the only way a script can add a new transaction in an atomic way.
    Typically, it is used to deploy a UAS.
 
-   Example 44. t_newtran usage
+   Example 46. t_newtran usage
 ...
 if (t_newtran()) {
     log("UAS logic");
@@ -1322,7 +1361,7 @@ if (t_newtran()) {
      * code - Reply code number.
      * reason_phrase - Reason string.
 
-   Example 45. t_reply usage
+   Example 47. t_reply usage
 ...
 t_reply("404", "Not found");
 ...
@@ -1335,7 +1374,7 @@ t_reply("404", "Not found");
    none was found. However this is safely (atomically) done using
    t_newtran.
 
-   Example 46. t_lookup_request usage
+   Example 48. t_lookup_request usage
 ...
 if (t_lookup_request()) {
     ...
@@ -1346,7 +1385,7 @@ if (t_lookup_request()) {
 
    Retransmits a reply sent previously by UAS transaction.
 
-   Example 47. t_retransmit_reply usage
+   Example 49. t_retransmit_reply usage
 ...
 t_retransmit_reply();
 ...
@@ -1356,7 +1395,7 @@ t_retransmit_reply();
    Remove transaction from memory (it will be first put on a wait timer to
    absorb delayed messages).
 
-   Example 48. t_release usage
+   Example 50. t_release usage
 ...
 t_release();
 ...
@@ -1371,7 +1410,7 @@ t_forward_nonack_tls(ip, port) t_forward_nonack_sctp(ip, port)
      * ip - IP address where the message should be sent.
      * port - Port number.
 
-   Example 49. t_forward_nonack usage
+   Example 51. t_forward_nonack usage
 ...
 t_forward_nonack("1.2.3.4", "5060");
 ...
@@ -1394,7 +1433,7 @@ t_forward_nonack("1.2.3.4", "5060");
 
    See also: fr_timer, fr_inv_timer, t_reset_fr().
 
-   Example 50. t_set_fr usage
+   Example 52. t_set_fr usage
 ...
 route {
         t_set_fr(10000); # set only fr invite timeout to 10s
@@ -1421,7 +1460,7 @@ branch_route[1] {
 
    See also: fr_timer, fr_inv_timer, t_set_fr.
 
-   Example 51. t_reset_fr usage
+   Example 53. t_reset_fr usage
 ...
 route {
 ...
@@ -1447,7 +1486,7 @@ route {
 
    See also: max_inv_lifetime, max_noninv_lifetime, t_reset_max_lifetime.
 
-   Example 52. t_set_max_lifetime usage
+   Example 54. t_set_max_lifetime usage
 ...
 route {
     if (src_ip=1.2.3.4)
@@ -1469,7 +1508,7 @@ route {
 
    See also: max_inv_lifetime, max_noninv_lifetime, t_set_max_lifetime.
 
-   Example 53. t_reset_max_lifetime usage
+   Example 55. t_reset_max_lifetime usage
 ...
 route {
 ...
@@ -1507,7 +1546,7 @@ route {
 
    See also: retr_timer1, retr_timer2, t_reset_retr().
 
-   Example 54. t_set_retr usage
+   Example 56. t_set_retr usage
 ...
 route {
         t_set_retr(250, 0); # set only T1 to 250 ms
@@ -1534,7 +1573,7 @@ branch_route[1] {
 
    See also: retr_timer1, retr_timer2, t_set_retr.
 
-   Example 55. t_reset_retr usage
+   Example 57. t_reset_retr usage
 ...
 route {
 ...
@@ -1550,7 +1589,7 @@ route {
 
    See also: auto_inv_100.
 
-   Example 56. t_set_auto_inv_100 usage
+   Example 58. t_set_auto_inv_100 usage
 ...
 route {
 ...
@@ -1564,7 +1603,7 @@ route {
    Returns true if the failure route is executed for a branch that did
    timeout. It can be used only from the failure_route.
 
-   Example 57. t_branch_timeout usage
+   Example 59. t_branch_timeout usage
 ...
 failure_route[0]{
         if (t_branch_timeout()){
@@ -1579,7 +1618,7 @@ failure_route[0]{
    receive at least one reply in the past (the "current" reply is not
    taken into account). It can be used only from the failure_route.
 
-   Example 58. t_branch_replied usage
+   Example 60. t_branch_replied usage
 ...
 failure_route[0]{
         if (t_branch_timeout()){
@@ -1596,7 +1635,7 @@ failure_route[0]{
    Returns true if at least one of the current transactions branches did
    timeout.
 
-   Example 59. t_any_timeout usage
+   Example 61. t_any_timeout usage
 ...
 failure_route[0]{
         if (!t_branch_timeout()){
@@ -1613,7 +1652,7 @@ failure_route[0]{
    receive some reply in the past. If called from a failure or onreply
    route, the "current" reply is not taken into account.
 
-   Example 60. t_any_replied usage
+   Example 62. t_any_replied usage
 ...
 onreply_route[0]{
         if (!t_any_replied()){
@@ -1627,7 +1666,7 @@ onreply_route[0]{
    Returns true if "code" is the final reply received (or locally
    generated) in at least one of the current transactions branches.
 
-   Example 61. t_grep_status usage
+   Example 63. t_grep_status usage
 ...
 onreply_route[0]{
         if (t_grep_status("486")){
@@ -1640,7 +1679,7 @@ onreply_route[0]{
 
    Returns true if the current transaction was canceled.
 
-   Example 62. t_is_canceled usage
+   Example 64. t_is_canceled usage
 ...
 failure_route[0]{
         if (t_is_canceled()){
@@ -1654,7 +1693,7 @@ failure_route[0]{
    Returns true if the current transaction has already been expired, i.e.
    the max_inv_lifetime/max_noninv_lifetime interval has already elapsed.
 
-   Example 63. t_is_expired usage
+   Example 65. t_is_expired usage
 ...
 failure_route[0]{
         if (t_is_expired()){
@@ -1675,7 +1714,7 @@ failure_route[0]{
    CANCELs were successfully sent to the pending branches, true if the
    INVITE was not found, and false in case of any error.
 
-   Example 64. t_relay_cancel usage
+   Example 66. t_relay_cancel usage
 if (method == CANCEL) {
         if (!t_relay_cancel()) {  # implicit drop if relaying was successful,
                                   # nothing to do
@@ -1702,7 +1741,7 @@ if (method == CANCEL) {
    overwritten with the flags of the INVITE. isflagset() can be used to
    check the flags of the previously forwarded INVITE in this case.
 
-   Example 65. t_lookup_cancel usage
+   Example 67. t_lookup_cancel usage
 if (method == CANCEL) {
         if (t_lookup_cancel()) {
                 log("INVITE transaction exists");
@@ -1732,7 +1771,7 @@ if (method == CANCEL) {
    Dropping replies works only if a new branch is added to the
    transaction, or it is explicitly replied in the script!
 
-   Example 66. t_drop_replies() usage
+   Example 68. t_drop_replies() usage
 ...
 failure_route[0]{
         if (t_check_status("5[0-9][0-9]")){
@@ -1763,7 +1802,7 @@ failure_route[0]{
    The transaction must be created by t_newtran() before calling
    t_save_lumps().
 
-   Example 67. t_save_lumps() usage
+   Example 69. t_save_lumps() usage
 route {
         ...
         t_newtran();
@@ -1833,7 +1872,7 @@ failure_route[1] {
 
    This function can be used from REQUEST_ROUTE.
 
-   Example 68. t_load_contacts usage
+   Example 70. t_load_contacts usage
 ...
 if (!t_load_contacts()) {
         sl_send_reply("500", "Server Internal Error - Cannot load contacts");
@@ -1874,7 +1913,7 @@ if (!t_load_contacts()) {
    anymore set. Based on that test, you can then use t_set_fr() function
    to set timers according to your needs.
 
-   Example 69. t_next_contacts usage
+   Example 71. t_next_contacts usage
 ...
 # First call after t_load_contacts() when transaction does not exist yet
 # and contacts should be available
@@ -1938,7 +1977,7 @@ Note
 
    See also: t_lookup_request(), t_lookup_cancel().
 
-   Example 70. t_check_trans usage
+   Example 72. t_check_trans usage
 if ( method == "CANCEL" && !t_check_trans())
         sl_reply("403", "cancel out of the blue forbidden");
 # note: in this example t_check_trans() can be replaced by t_lookup_cancel()
@@ -1953,7 +1992,7 @@ if ( method == "CANCEL" && !t_check_trans())
 
    See also: disable_6xx_block.
 
-   Example 71. t_set_disable_6xx usage
+   Example 73. t_set_disable_6xx usage
 ...
 route {
 ...
@@ -1968,7 +2007,7 @@ route {
 
    See also: use_dns_failover.
 
-   Example 72. t_set_disable_failover usage
+   Example 74. t_set_disable_failover usage
 ...
 route {
 ...
@@ -1999,7 +2038,7 @@ route {
      * hostport - address in "host:port" format. It can be given via an
        AVP.
 
-   Example 73. t_replicate usage
+   Example 75. t_replicate usage
 ...
 # sent to 1.2.3.4:5060 over tcp
 t_replicate("sip:1.2.3.4:5060;transport=tcp");
@@ -2034,7 +2073,7 @@ t_replicate_to_udp("1.2.3.4", "5060");
             effect anymore).
           + 0x04 - disable dns failover.
 
-   Example 74. t_replicate usage
+   Example 76. t_replicate usage
 ...
 # sent to 1.2.3.4:5060 over tcp
 t_relay_to("tcp:1.2.3.4:5060");
@@ -2046,6 +2085,27 @@ t_relay_to("tls:1.2.3.4");
 t_relay_to("0x01");
 ...
 
+1.5.38.  t_set_no_e2e_cancel_reason(0|1)
+
+   Enables/disables reason header (RFC 3326) copying from the triggering
+   received CANCEL to the generated hop-by-hop CANCEL. 0 enables and 1
+   disables.
+
+   It overrides the e2e_cancel_reason setting (module parameter) for the
+   current transaction.
+
+   See also: e2e_cancel_reason.
+
+   Example 77. t_set_no_e2e_cancel_reason usage
+...
+route {
+...
+        if (src_ip!=10.0.0.0/8) #  don't trust CANCELs from the outside
+                t_set_no_e2e_cancel_reason(1); # turn off CANCEL reason header c
+opying
+...
+}
+
 1.6. TM Module API
 
    Revision History

+ 14 - 5
modules/tm/config.c

@@ -24,10 +24,9 @@
  *  2008-02-05	adapting tm module for the configuration framework (Miklos)
  */
 
-/*!
- * \file 
- * \brief TM :: Configuration
- * \ingroup tm
+/** TM :: Runtime configuration variables.
+ * @file
+ * @ingroup tm
  */
 
 
@@ -94,7 +93,11 @@ struct cfg_group_tm	default_tm_cfg = {
 	1,	/* cancel_b_method used for e2e and 6xx cancels*/
 	1,	/* reparse_on_dns_failover */
 	0, /* disable_6xx, by default off */
-	0  /* local_ack_mode, default 0 (rfc3261 conformant) */
+	0,  /* local_ack_mode, default 0 (rfc3261 conformant) */
+	1, /* local_cancel_reason -- add Reason header to locally generated
+		  CANCELs; on by default */
+	1  /* e2e_cancel_reason -- copy the Reason headers from incoming CANCELs
+		  into the corresp. hop-by-hop CANCELs, on by default */
 };
 
 void	*tm_cfg = &default_tm_cfg;
@@ -196,5 +199,11 @@ cfg_def_t	tm_cfg_def[] = {
 		" it is not set to 0 but allows dealing with NATed contacts in some "
 		"simple cases)"
 		},
+	{"local_cancel_reason",	CFG_VAR_INT | CFG_ATOMIC,	0, 1, 0, 0,
+		"if set to 1, a Reason header is added to locally generated CANCELs"
+		" (see RFC3326)" },
+	{"e2e_cancel_reason",	CFG_VAR_INT | CFG_ATOMIC,	0, 1, 0, 0,
+		"if set to 1, Reason headers from received CANCELs are copied into"
+		" the corresponding generated hop-by-hop CANCELs"},
 	{0, 0, 0, 0, 0, 0}
 };

+ 5 - 4
modules/tm/config.h

@@ -21,10 +21,9 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
-/*!
- * \file 
- * \brief TM :: Configuration
- * \ingroup tm
+/** TM :: Runtime configuration variables.
+ * @file
+ * @ingroup tm
  */
 
 
@@ -136,6 +135,8 @@ struct cfg_group_tm {
 	int	reparse_on_dns_failover;
 	int disable_6xx;
 	int local_ack_mode;
+	int local_cancel_reason;
+	int e2e_cancel_reason;
 };
 
 extern struct cfg_group_tm	default_tm_cfg;

+ 58 - 23
modules/tm/doc/functions.xml

@@ -1213,7 +1213,9 @@ if (!t_next_contacts()) {
 				INVITE transactions) it will return true if the corresponding
 				INVITE transaction is found and still active and false if not.
 				</para>
-				<note>Note that the e2e ACK matching is more of a hint
+				<note>
+				<para>
+				Note that the e2e ACK matching is more of a hint
 				then a certainty. A delayed e2e ACK might arrive after the
 				transaction wait time elapses, when the INVITE transaction no
 				longer exists and thus would not match anything. There are
@@ -1221,6 +1223,7 @@ if (!t_next_contacts()) {
 				for e2e ACK matching (since this is not needed for a statefull
 				proxy and it requires additional memory, tm will not keep this
 				information unless needed by some other module or callbacks).
+				</para>
 				</note>
 			</listitem>
 			<listitem>
@@ -1321,27 +1324,27 @@ route {
 	<para>
 		There are several function prototypes:
 		<itemizedlist>
-		<listitem>
+		<listitem><para>
 	    <function>t_replicate(uri)</function>,
-		</listitem>
-		<listitem>
+		</para></listitem>
+		<listitem><para>
 	    <function>t_replicate(host, port)</function>,
-		</listitem>
-		<listitem>
+		</para></listitem>
+		<listitem><para>
 	    <function>t_replicat_udp(host, port)</function>
-		</listitem>
-		<listitem>
+		</para></listitem>
+		<listitem><para>
 	    <function>t_replicate_tcp(host, port)</function>
-		</listitem>
-		<listitem>
+		</para></listitem>
+		<listitem><para>
 	    <function>t_replicate_tls(host, port)</function>
-		</listitem>
-		<listitem>
+		</para></listitem>
+		<listitem><para>
 	    <function>t_replicate_sctp(host, port)</function>
-		</listitem>
-		<listitem>
+		</para></listitem>
+		<listitem><para>
 	    <function>t_replicate_to(proto, hostport)</function>
-		</listitem>
+		</para></listitem>
 		</itemizedlist>
 	</para>
 	<para>Meaning of the parameters is as follows:</para>
@@ -1397,18 +1400,18 @@ t_replicate_to_udp("1.2.3.4", "5060");
 	<para>
 		There are several function prototypes:
 		<itemizedlist>
-		<listitem>
+		<listitem><para>
 	    <function>t_relay_to()</function>,
-		</listitem>
-		<listitem>
+		</para></listitem>
+		<listitem><para>
 	    <function>t_relay_to(proxy)</function>,
-		</listitem>
-		<listitem>
+		</para></listitem>
+		<listitem><para>
 	    <function>t_relay_to(flags)</function>
-		</listitem>
-		<listitem>
+		</para></listitem>
+		<listitem><para>
 	    <function>t_relay_to(proxy, flags)</function>
-		</listitem>
+		</para></listitem>
 		</itemizedlist>
 	</para>
 	<para>Meaning of the parameters is as follows:</para>
@@ -1457,4 +1460,36 @@ t_relay_to("0x01");
 	</example>
     </section>
 
+
+	<section id="t_set_no_e2e_cancel_reason">
+	<title>
+		<function>t_set_no_e2e_cancel_reason(0|1)</function>
+	</title>
+	<para>
+		Enables/disables reason header (RFC 3326) copying from the triggering
+		received CANCEL to the generated hop-by-hop CANCEL. 0 enables and
+		1 disables.
+	</para>
+	<para>
+		It overrides the <varname>e2e_cancel_reason</varname> setting (module
+		 parameter) for the current transaction.
+	</para>
+	<para>
+		See also: <varname>e2e_cancel_reason</varname>.
+	</para>
+	<example>
+		<title><function>t_set_no_e2e_cancel_reason</function> usage</title>
+		<programlisting>
+...
+route {
+...
+	if (src_ip!=10.0.0.0/8) #  don't trust CANCELs from the outside
+		t_set_no_e2e_cancel_reason(1); # turn off CANCEL reason header copying
+...
+}
+		</programlisting>
+	</example>
+	</section>
+
+
 </section>

+ 66 - 2
modules/tm/doc/params.xml

@@ -45,9 +45,11 @@ modparam("tm", "fr_timer", 10000)
 	</para>
 	<para>
 	</para>
+	<para>
 		Note: this timer can be restarted when a provisional response is
 		received. For more details see
 		<varname>restart_fr_on_each_reply</varname>.
+	</para>
 	<para>
 	    Default value is 120000 ms (120 seconds).
 	</para>
@@ -1094,11 +1096,11 @@ modparam("tm", "disable_6xx_block", 1)
 		reply.
 		</para></listitem>
 	</itemizedlist>
-	<note>
+	<note><para>
 	Mode 1 and 2 break the rfc, but are useful to deal with some simple UAs
 	behind the NAT cases (no different routing for the ACK and the contact 
 	contains an address behind the NAT).
-	</note>
+	</para></note>
 	<para>
 		The default value is 0 (rfc conformant behaviour).
 	</para>
@@ -1172,4 +1174,66 @@ modparam("tm", "failure_reply_mode", 3)
 	</example>
 	</section>
 
+
+	<section id="local_cancel_reason">
+		<title><varname>local_cancel_reason</varname> (boolean)</title>
+		<para>
+			Enables/disables adding reason headers (RFC 3326) for CANCELs
+			generated due to receiving a final reply. The reason header added
+			will look like: "Reason: SIP;cause=&lt;final_reply_code&gt;".
+		</para>
+		<para>
+			Default value is 1 (enabled).
+		</para>
+		<para>
+			Can be set at runtime, e.g.:
+			<programlisting>
+	$ sercmd cfg.set_now_int tm local_cancel_reason 0
+			</programlisting>
+		</para>
+		<para>
+			See also: <varname>e2e_cancel_reason</varname>.
+		</para>
+		<example>
+			<title>Set <varname>local_cancel_reason</varname> parameter</title>
+			<programlisting>
+...
+modparam("tm", "local_cancel_reason", "0")
+...
+			</programlisting>
+		</example>
+	</section>
+
+
+	<section id="e2e_cancel_reason">
+		<title><varname>e2e_cancel_reason</varname> (boolean)</title>
+		<para>
+			Enables/disables adding reason headers (RFC 3326) for CANCELs
+			generated due to a received CANCEL.  If enabled the reason headers
+			from received CANCELs will be copied into the generated hop-by-hop
+			CANCELs.
+		</para>
+		<para>
+			Default value is 1 (enabled).
+		</para>
+		<para>
+			Can be changed at runtime, e.g.:
+			<programlisting>
+	$ sercmd cfg.set_now_int tm e2e_cancel_reason 0
+			</programlisting>
+		</para>
+		<para>
+			See also: <function>t_set_no_e2e_cancel_reason()</function> and
+						<varname>local_cancel_reason</varname>.
+		</para>
+		<example>
+			<title>Set <varname>e2e_cancel_reason</varname> parameter</title>
+			<programlisting>
+...
+modparam("tm", "e2e_cancel_reason", "0")
+...
+			</programlisting>
+		</example>
+	</section>
+
 </section>

+ 8 - 1
modules/tm/doc/tm.xml

@@ -1,6 +1,13 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" 
-   "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+	[ <!ENTITY % local.common.attrib
+	 "xmlns:xi CDATA #FIXED 'http://www.w3.org/2001/XInclude'">
+	 <!-- Include general documentation entities -->
+	 <!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
+	 %docentities;
+	]
+>
 
 <section id="tm" xmlns:xi="http://www.w3.org/2001/XInclude">
     <sectioninfo>

+ 4 - 0
modules/tm/h_table.c

@@ -141,6 +141,10 @@ void free_cell( struct cell* dead_cell )
 		sip_msg_free_unsafe( dead_cell->uas.request );
 	if ( dead_cell->uas.response.buffer )
 		shm_free_unsafe( dead_cell->uas.response.buffer );
+#ifdef CANCEL_REASON_SUPPORT
+	if (unlikely(dead_cell->uas.cancel_reas))
+		shm_free_unsafe(dead_cell->uas.cancel_reas);
+#endif /* CANCEL_REASON_SUPPORT */
 
 	/* callbacks */
 	for( cbs=(struct tm_callback*)dead_cell->tmcb_hl.first ; cbs ; ) {

+ 10 - 4
modules/tm/h_table.h

@@ -40,10 +40,9 @@
  *              inlined often used functions (andrei)
  */
 
-/*!
- * \file 
- * \brief TM :: 
- * \ingroup tm
+/**  TM :: hash table, flags and other general defines.
+ * @file 
+ * @ingroup tm
  */
 
 
@@ -192,6 +191,10 @@ typedef struct ua_server
 	 * we need them for dialog-wise matching of ACKs;
 	 * the pointer shows to shmem-ed reply */
 	str				 local_totag;
+#ifdef CANCEL_REASON_SUPPORT
+	struct cancel_reason* cancel_reas; /* pointer to cancel reason, used
+										  for e2e cancels */
+#endif /* CANCEL_REASON_SUPPORT */
 	unsigned int     status;
 }ua_server_type;
 
@@ -281,6 +284,9 @@ struct totag_elem {
 
 #define T_DISABLE_6xx (1<<8) /* treat 6xx as a normal reply */
 #define T_DISABLE_FAILOVER (1<<9) /* don't perform dns failover */
+#ifdef CANCEL_REASON_SUPPORT
+#define T_NO_E2E_CANCEL_REASON (1<<10) /* don't propagate CANCEL Reason */
+#endif /* CANCEL_REASON_SUPPORT */
 #define T_DONT_FORK   (T_CANCELED|T_6xx)
 
 /* unsigned short should be enough for a retr. timer: max. 65535 ticks =>

+ 40 - 21
modules/tm/t_cancel.c

@@ -46,6 +46,7 @@
  *              reflecting its purpose
  *             prepare_to_cancel() takes now an additional skip_branches
  *              bitmap parameter (andrei)
+ * 2010-02-26  cancel reason (rfc3326) basic support (andrei)
  */
 
 #include <stdio.h> /* for FILE* in fifo_uac_cancel */
@@ -102,13 +103,14 @@ void prepare_to_cancel(struct cell *t, branch_bm_t *cancel_bm,
 
 /* cancel branches scheduled for deletion
  * params: t          - transaction
- *          cancel_bm - bitmap with the branches that are supposed to be 
- *                       canceled 
+ *          cancel_data - structure filled with the cancel bitmap (bitmap with
+ *                       the branches that are supposed to be canceled) and
+ *                       the cancel reason.
  *          flags     - how_to_cancel flags, see cancel_branch()
  * returns: bitmap with the still active branches (on fr timer)
- * WARNING: always fill cancel_bm using prepare_to_cancel(), supplying values
- *          in any other way is a bug*/
-int cancel_uacs( struct cell *t, branch_bm_t cancel_bm, int flags)
+ * WARNING: always fill cancel_data->cancel_bitmap using prepare_to_cancel(),
+ *          supplying values in any other way is a bug*/
+int cancel_uacs( struct cell *t, struct cancel_info* cancel_data, int flags)
 {
 	int i;
 	int ret;
@@ -117,10 +119,13 @@ int cancel_uacs( struct cell *t, branch_bm_t cancel_bm, int flags)
 	ret=0;
 	/* cancel pending client transactions, if any */
 	for( i=0 ; i<t->nr_of_outgoings ; i++ ) 
-		if (cancel_bm & (1<<i)){
+		if (cancel_data->cancel_bitmap & (1<<i)){
 			r=cancel_branch(
 				t,
 				i,
+#ifdef CANCEL_REASON_SUPPORT
+				&cancel_data->reason,
+#endif /* CANCEL_REASON_SUPPORT */
 				flags | ((t->uac[i].request.buffer==NULL)?
 					F_CANCEL_B_FAKE_REPLY:0) /* blind UAC? */
 			);
@@ -131,7 +136,7 @@ int cancel_uacs( struct cell *t, branch_bm_t cancel_bm, int flags)
 
 int cancel_all_uacs(struct cell *trans, int how)
 {
-	branch_bm_t cancel_bm;
+	struct cancel_info cancel_data;
 	int i,j;
 
 #ifdef EXTRA_DEBUG
@@ -139,10 +144,10 @@ int cancel_all_uacs(struct cell *trans, int how)
 #endif
 	DBG("Canceling T@%p [%u:%u]\n", trans, trans->hash_index, trans->label);
 	
-	cancel_bm=0;
-	prepare_to_cancel(trans, &cancel_bm, 0);
+	init_cancel_info(&cancel_data);
+	prepare_to_cancel(trans, &cancel_data.cancel_bitmap, 0);
 	 /* tell tm to cancel the call */
-	i=cancel_uacs(trans, cancel_bm, how);
+	i=cancel_uacs(trans, &cancel_data, how);
 	
 	if (how & F_CANCEL_UNREF)
 #ifndef TM_DEL_UNREF
@@ -172,6 +177,7 @@ int cancel_all_uacs(struct cell *trans, int how)
  *
  * params:  t - transaction
  *          branch - branch number to be canceled
+ *          reason - cancel reason structure
  *          flags - howto cancel: 
  *                   F_CANCEL_B_KILL - will completely stop the 
  *                     branch (stops the timers), use with care
@@ -202,13 +208,17 @@ int cancel_all_uacs(struct cell *trans, int how)
  *          - checking for buffer==0 under REPLY_LOCK is no enough, an 
  *           atomic_cmpxhcg or atomic_get_and_set _must_ be used.
  */
-int cancel_branch( struct cell *t, int branch, int flags )
+int cancel_branch( struct cell *t, int branch,
+	#ifdef CANCEL_REASON_SUPPORT
+					struct cancel_reason* reason,
+	#endif /* CANCEL_REASON_SUPPORT */
+					int flags )
 {
 	char *cancel;
 	unsigned int len;
 	struct retr_buf *crb, *irb;
 	int ret;
-	branch_bm_t tmp_bm;
+	struct cancel_info tmp_cd;
 	void* pcbuf;
 
 	crb=&t->uac[branch].local_cancel;
@@ -236,7 +246,7 @@ int cancel_branch( struct cell *t, int branch, int flags )
 			atomic_set_long(pcbuf, 0);
 			if (flags & F_CANCEL_B_FAKE_REPLY){
 				LOCK_REPLIES(t);
-				if (relay_reply(t, FAKED_REPLY, branch, 487, &tmp_bm, 1) == 
+				if (relay_reply(t, FAKED_REPLY, branch, 487, &tmp_cd, 1) == 
 										RPS_ERROR){
 					return -1;
 				}
@@ -257,7 +267,7 @@ int cancel_branch( struct cell *t, int branch, int flags )
 				if (flags & F_CANCEL_B_FAKE_REPLY){
 					stop_rb_timers( irb ); /* stop even the fr timer */
 					LOCK_REPLIES(t);
-					if (relay_reply(t, FAKED_REPLY, branch, 487, &tmp_bm, 1)== 
+					if (relay_reply(t, FAKED_REPLY, branch, 487, &tmp_cd, 1)== 
 											RPS_ERROR){
 						return -1;
 					}
@@ -272,10 +282,19 @@ int cancel_branch( struct cell *t, int branch, int flags )
 
 	if (cfg_get(tm, tm_cfg, reparse_invite)) {
 		/* build the CANCEL from the INVITE which was sent out */
-		cancel = build_local_reparse(t, branch, &len, CANCEL, CANCEL_LEN, &t->to);
+		cancel = build_local_reparse(t, branch, &len, CANCEL, CANCEL_LEN,
+									 &t->to
+	#ifdef CANCEL_REASON_SUPPORT
+									 , reason
+	#endif /* CANCEL_REASON_SUPPORT */
+									 );
 	} else {
-		/* build the CANCEL from the reveived INVITE */
-		cancel = build_local(t, branch, &len, CANCEL, CANCEL_LEN, &t->to);
+		/* build the CANCEL from the received INVITE */
+		cancel = build_local(t, branch, &len, CANCEL, CANCEL_LEN, &t->to
+	#ifdef CANCEL_REASON_SUPPORT
+								, reason
+	#endif /* CANCEL_REASON_SUPPORT */
+								);
 	}
 	if (!cancel) {
 		LOG(L_ERR, "ERROR: attempt to build a CANCEL failed\n");
@@ -333,7 +352,7 @@ void rpc_cancel(rpc_t* rpc, void* c)
 {
 	struct cell *trans;
 	static char cseq[128], callid[128];
-	branch_bm_t cancel_bm;
+	struct cancel_info cancel_data;
 	int i,j;
 
 	str cseq_s;   /* cseq */
@@ -341,7 +360,7 @@ void rpc_cancel(rpc_t* rpc, void* c)
 
 	cseq_s.s=cseq;
 	callid_s.s=callid;
-	cancel_bm=0;
+	init_cancel_info(&cancel_data);
 
 	if (rpc->scan(c, "SS", &callid_s, &cseq_s) < 2) {
 		rpc->fault(c, 400, "Callid and CSeq expected as parameters");
@@ -354,10 +373,10 @@ void rpc_cancel(rpc_t* rpc, void* c)
 		return;
 	}
 	/*  find the branches that need cancel-ing */
-	prepare_to_cancel(trans, &cancel_bm, 0);
+	prepare_to_cancel(trans, &cancel_data.cancel_bitmap, 0);
 	 /* tell tm to cancel the call */
 	DBG("Now calling cancel_uacs\n");
-	i=cancel_uacs(trans, cancel_bm, 0); /* don't fake 487s, 
+	i=cancel_uacs(trans, &cancel_data, 0); /* don't fake 487s, 
 										 just wait for timeout */
 	
 	/* t_lookup_callid REF`d the transaction for us, we must UNREF here! */

+ 8 - 2
modules/tm/t_cancel.h

@@ -35,6 +35,7 @@
  *  2009-07-14  should_cancel_branch() renamed to prepare_cancel_branch() to
  *               better reflect its purpose
  *              which_cancel() renamed to prepare_to_cancel() (andrei)
+ * 2010-02-26  cancel reason (rfc3326) basic support (andrei)
  */
 
 
@@ -46,6 +47,7 @@
 #include "../../atomic_ops.h"
 #include "defs.h"
 #include "h_table.h"
+#include "t_reply.h"
 
 
 /* a buffer is empty but cannot be used by anyone else;
@@ -79,9 +81,13 @@
 
 
 void prepare_to_cancel(struct cell *t, branch_bm_t *cancel_bm, branch_bm_t s);
-int cancel_uacs( struct cell *t, branch_bm_t cancel_bm, int flags );
+int cancel_uacs( struct cell *t, struct cancel_info* cancel_data, int flags );
 int cancel_all_uacs(struct cell *trans, int how);
-int cancel_branch( struct cell *t, int branch, int flags );
+int cancel_branch( struct cell *t, int branch,
+#ifdef CANCEL_REASON_SUPPORT
+					struct cancel_reason* reason,
+#endif /* CANCEL_REASON_SUPPORT */
+					int flags );
 
 typedef int(*cancel_uacs_f)( struct cell *t, branch_bm_t cancel_bm,
 								int flags );

+ 136 - 3
modules/tm/t_fwd.c

@@ -929,7 +929,11 @@ int e2e_cancel_branch( struct sip_msg *cancel_msg, struct cell *t_cancel,
 			"thus lumps are not applied to the message!\n");
 		}
 		shbuf=build_local_reparse( t_invite, branch, &len, CANCEL,
-									CANCEL_LEN, &t_invite->to);
+									CANCEL_LEN, &t_invite->to
+#ifdef CANCEL_REASON_SUPPORT
+									, 0
+#endif /* CANCEL_REASON_SUPPORT */
+									);
 		if (unlikely(!shbuf)) {
 			LOG(L_ERR, "e2e_cancel_branch: printing e2e cancel failed\n");
 			ret=ser_error=E_OUT_OF_MEM;
@@ -962,13 +966,118 @@ error:
 
 
 
-void e2e_cancel( struct sip_msg *cancel_msg, 
+#ifdef CANCEL_REASON_SUPPORT
+/** create a cancel reason structure packed into a single shm. block.
+  * From a cause and a pointer to a str or cancel_msg, build a
+  * packed cancel reason structure (CANCEL_REAS_PACKED_HDRS), using a
+  * single memory allocation (so that it can be freed by a simple shm_free().
+  * @param cause - cancel cause, @see cancel_reason for more details.
+  * @param data - depends on the cancel cause.
+  * @return pointer to shm. packed cancel reason struct. on success,
+  *        0 on error
+  */
+static struct cancel_reason* cancel_reason_pack(short cause, void* data,
+													struct cell* t)
+{
+	char* d;
+	struct cancel_reason* cr;
+	int reason_len;
+	int code_len;
+	struct hdr_field *reas1, *reas_last, *hdr;
+	str* txt;
+	struct sip_msg* e2e_cancel;
+	
+	if (likely(cause != CANCEL_REAS_UNKNOWN)){
+		reason_len = 0;
+		txt = 0;
+		e2e_cancel = 0;
+		reas1 = 0;
+		reas_last = 0;
+		if (likely(cause == CANCEL_REAS_RCVD_CANCEL &&
+					data && !(t->flags & T_NO_E2E_CANCEL_REASON))) {
+			/* parse the entire cancel, to get all the Reason headers */
+			e2e_cancel = data;
+			parse_headers(e2e_cancel, HDR_EOH_F, 0);
+			for(hdr=get_hdr(e2e_cancel, HDR_REASON_T), reas1=hdr;
+					hdr; hdr=next_sibling_hdr(hdr)) {
+				/* hdr->len includes CRLF */
+				reason_len += hdr->len;
+				reas_last=hdr;
+			}
+		} else if (likely(cause > 0 &&
+					cfg_get(tm, tm_cfg, local_cancel_reason))){
+			txt = (str*) data;
+			/* Reason: SIP;cause=<reason->cause>[;text=<reason->u.text.s>] */
+			reason_len = REASON_PREFIX_LEN + USHORT2SBUF_MAX_LEN +
+				((txt && txt->s)?
+					REASON_TEXT_LEN + 1 + txt->len + 1 : 0) +
+				CRLF_LEN;
+		} else if (cause == CANCEL_REAS_PACKED_HDRS &&
+					!(t->flags & T_NO_E2E_CANCEL_REASON) && data) {
+			txt = (str*) data;
+			reason_len = txt?txt->len:0;
+		} else if (unlikely(cause < CANCEL_REAS_MIN)) {
+			BUG("unhandled reason cause %d\n", cause);
+			goto error;
+		}
+		
+		if (unlikely(reason_len == 0))
+			return 0; /* nothing to do, no reason */
+		cr = shm_malloc(sizeof(struct cancel_reason) + reason_len);
+		if (unlikely(cr == 0))
+				goto error;
+		d = (char*)cr +sizeof(*cr);
+		cr->cause = CANCEL_REAS_PACKED_HDRS;
+		cr->u.packed_hdrs.s = d;
+		cr->u.packed_hdrs.len = reason_len;
+		
+		if (cause == CANCEL_REAS_RCVD_CANCEL) {
+			for(hdr=reas1; hdr; hdr=next_sibling_hdr(hdr)) {
+				/* hdr->len includes CRLF */
+				append_str(d, hdr->name.s, hdr->len);
+				if (likely(hdr==reas_last))
+					break;
+			}
+		} else if (likely(cause > 0)) {
+			append_str(d, REASON_PREFIX, REASON_PREFIX_LEN);
+			code_len=ushort2sbuf(cause, d, reason_len - 
+									(int)(d - (char*)cr - sizeof(*cr)));
+			if (unlikely(code_len==0)) {
+				shm_free(cr);
+				cr = 0;
+				BUG("not enough space to write reason code");
+				goto error;
+			}
+			d+=code_len;
+			if (txt && txt->s){
+				append_str(d, REASON_TEXT, REASON_TEXT_LEN);
+				*d='"'; d++;
+				append_str(d, txt->s, txt->len);
+				*d='"'; d++;
+			}
+			append_str(d, CRLF, CRLF_LEN);
+		} else if (cause == CANCEL_REAS_PACKED_HDRS) {
+			append_str(d, txt->s, txt->len);
+		}
+		return cr;
+	}
+error:
+	return 0;
+}
+#endif /* CANCEL_REASON_SUPPORT */
+
+
+
+void e2e_cancel( struct sip_msg *cancel_msg,
 	struct cell *t_cancel, struct cell *t_invite )
 {
 	branch_bm_t cancel_bm;
 #ifndef E2E_CANCEL_HOP_BY_HOP
 	branch_bm_t tmp_bm;
-#endif
+#elif defined (CANCEL_REASON_SUPPORT)
+	struct cancel_reason* reason;
+	int free_reason;
+#endif /* E2E_CANCEL_HOP_BY_HOP */
 	int i;
 	int lowest_error;
 	int ret;
@@ -1013,6 +1122,21 @@ void e2e_cancel( struct sip_msg *cancel_msg,
 	 * have 0 branches and we check for the branch number in 
 	 * t_reply_matching() ).
 	 */
+#ifdef CANCEL_REASON_SUPPORT
+	free_reason = 0;
+	reason = 0;
+	if (likely(t_invite->uas.cancel_reas == 0)){
+		reason = cancel_reason_pack(CANCEL_REAS_RCVD_CANCEL, cancel_msg,
+									t_invite);
+		/* set if not already set */
+		if (unlikely(reason &&
+					atomic_cmpxchg_long((void*)&t_invite->uas.cancel_reas,
+										0, (long)reason) != 0)) {
+			/* already set, failed to re-set it */
+			free_reason = 1;
+		}
+	}
+#endif /* CANCEL_REASON_SUPPORT */
 	for (i=0; i<t_invite->nr_of_outgoings; i++)
 		if (cancel_bm & (1<<i)) {
 			/* it's safe to get the reply lock since e2e_cancel is
@@ -1022,6 +1146,9 @@ void e2e_cancel( struct sip_msg *cancel_msg,
 			ret=cancel_branch(
 				t_invite,
 				i,
+#ifdef CANCEL_REASON_SUPPORT
+				reason,
+#endif /* CANCEL_REASON_SUPPORT */
 				cfg_get(tm,tm_cfg, cancel_b_flags)
 					| ((t_invite->uac[i].request.buffer==NULL)?
 						F_CANCEL_B_FAKE_REPLY:0) /* blind UAC? */
@@ -1029,6 +1156,12 @@ void e2e_cancel( struct sip_msg *cancel_msg,
 			if (ret<0) cancel_bm &= ~(1<<i);
 			if (ret<lowest_error) lowest_error=ret;
 		}
+#ifdef CANCEL_REASON_SUPPORT
+	if (unlikely(free_reason)) {
+		/* reason was not set as the global reason => free it */
+		shm_free(reason);
+	}
+#endif /* CANCEL_REASON_SUPPORT */
 #else /* ! E2E_CANCEL_HOP_BY_HOP */
 	/* fix label -- it must be same for reply matching (the label is part of
 	 * the generated via branch for the cancels sent upstream and if it

+ 4 - 0
modules/tm/t_lookup.c

@@ -1266,6 +1266,10 @@ static inline void init_new_t(struct cell *new_cell, struct sip_msg *p_msg)
 					(!cfg_get(tm, tm_cfg, tm_auto_inv_100) -1);
 		new_cell->flags|=T_DISABLE_6xx &
 					(!cfg_get(tm, tm_cfg, disable_6xx) -1);
+#ifdef CANCEL_REASON_SUPPORT
+		new_cell->flags|=T_NO_E2E_CANCEL_REASON &
+					(!!cfg_get(tm, tm_cfg, e2e_cancel_reason) -1);
+#endif /* CANCEL_REASON_SUPPORT */
 		/* reset flags */
 		new_cell->flags &=
 			(~ get_msgid_val(user_cell_reset_flags, p_msg->id, int));

+ 168 - 11
modules/tm/t_msgbuilder.c

@@ -46,6 +46,7 @@
  *               resolving nexthop twice (andrei)
  * 2007-05-28: build_local_reparse() is introdued: it uses the outgoing
  *             INVITE as a source to construct a CANCEL or ACK (Miklos)
+ * 2010-02-26  cancel reason (rfc3326) basic support (andrei)
  */
 
 #include "defs.h"
@@ -72,6 +73,7 @@
 #include "../../cfg_core.h" /* cfg_get(core, core_cfg, use_dns_failover) */
 #endif
 
+
 /* convenience macros */
 #define memapp(_d,_s,_len) \
 	do{\
@@ -84,7 +86,11 @@
    customers of this function are local ACK and local CANCEL
  */
 char *build_local(struct cell *Trans,unsigned int branch,
-	unsigned int *len, char *method, int method_len, str *to)
+	unsigned int *len, char *method, int method_len, str *to
+#ifdef CANCEL_REASON_SUPPORT
+	, struct cancel_reason* reason
+#endif /* CANCEL_REASON_SUPPORT */
+	)
 {
 	char                *cancel_buf, *p, *via;
 	unsigned int         via_len;
@@ -94,6 +100,10 @@ char *build_local(struct cell *Trans,unsigned int branch,
 	str branch_str;
 	str via_id;
 	struct hostport hp;
+#ifdef CANCEL_REASON_SUPPORT
+	int reason_len, code_len;
+	struct hdr_field *reas1, *reas_last;
+#endif /* CANCEL_REASON_SUPPORT */
 
 	/* init */
 	via_id.s=0;
@@ -157,7 +167,40 @@ char *build_local(struct cell *Trans,unsigned int branch,
 		*len += user_agent_hdr.len + CRLF_LEN;
 	}
 	/* Content Length, EoM */
-	*len+=CONTENT_LENGTH_LEN+1 + CRLF_LEN + CRLF_LEN;
+	*len+=CONTENT_LENGTH_LEN+1 + CRLF_LEN;
+#ifdef CANCEL_REASON_SUPPORT
+	reason_len = 0;
+	reas1 = 0;
+	reas_last = 0;
+	/* compute reason size (if no reason or disabled => reason_len == 0)*/
+	if (reason && reason->cause != CANCEL_REAS_UNKNOWN){
+		if (likely(reason->cause > 0 &&
+					cfg_get(tm, tm_cfg, local_cancel_reason))){
+			/* Reason: SIP;cause=<reason->cause>[;text=<reason->u.text.s>] */
+			reason_len = REASON_PREFIX_LEN + USHORT2SBUF_MAX_LEN +
+				(reason->u.text.s?
+					REASON_TEXT_LEN + 1 + reason->u.text.len + 1 : 0) +
+				CRLF_LEN;
+		} else if (likely(reason->cause == CANCEL_REAS_PACKED_HDRS &&
+					!(Trans->flags & T_NO_E2E_CANCEL_REASON))) {
+			reason_len = reason->u.packed_hdrs.len;
+		} else if (reason->cause == CANCEL_REAS_RCVD_CANCEL &&
+					reason->u.e2e_cancel &&
+					!(Trans->flags & T_NO_E2E_CANCEL_REASON)) {
+			/* parse the entire cancel, to get all the Reason headers */
+			parse_headers(reason->u.e2e_cancel, HDR_EOH_F, 0);
+			for(hdr=get_hdr(reason->u.e2e_cancel, HDR_REASON_T), reas1=hdr;
+					hdr; hdr=next_sibling_hdr(hdr)) {
+				/* hdr->len includes CRLF */
+				reason_len += hdr->len;
+				reas_last=hdr;
+			}
+		} else if (unlikely(reason->cause < CANCEL_REAS_MIN))
+			BUG("unhandled reason cause %d\n", reason->cause);
+	}
+	*len+= reason_len;
+#endif /* CANCEL_REASON_SUPPORT */
+	*len+= CRLF_LEN; /* end of msg. */
 
 	cancel_buf=shm_malloc( *len+1 );
 	if (!cancel_buf)
@@ -197,9 +240,38 @@ char *build_local(struct cell *Trans,unsigned int branch,
 		append_str(p, user_agent_hdr.s, user_agent_hdr.len );
 		append_str(p, CRLF, CRLF_LEN );
 	}
-	/* Content Length, EoM */
-	append_str(p, CONTENT_LENGTH "0" CRLF CRLF ,
-		CONTENT_LENGTH_LEN+1 + CRLF_LEN + CRLF_LEN);
+	/* Content Length */
+	append_str(p, CONTENT_LENGTH "0" CRLF, CONTENT_LENGTH_LEN + 1 + CRLF_LEN);
+#ifdef CANCEL_REASON_SUPPORT
+	/* add reason if needed */
+	if (reason_len) {
+		if (likely(reason->cause > 0)) {
+			append_str(p, REASON_PREFIX, REASON_PREFIX_LEN);
+			code_len=ushort2sbuf(reason->cause, p,
+									*len-(int)(p-cancel_buf));
+			if (unlikely(code_len==0))
+				BUG("not enough space to write reason code");
+			p+=code_len;
+			if (reason->u.text.s){
+				append_str(p, REASON_TEXT, REASON_TEXT_LEN);
+				*p='"'; p++;
+				append_str(p, reason->u.text.s, reason->u.text.len);
+				*p='"'; p++;
+			}
+			append_str(p, CRLF, CRLF_LEN);
+		} else if (likely(reason->cause == CANCEL_REAS_PACKED_HDRS)) {
+			append_str(p, reason->u.packed_hdrs.s, reason->u.packed_hdrs.len);
+		} else if (reason->cause == CANCEL_REAS_RCVD_CANCEL) {
+			for(hdr=reas1; hdr; hdr=next_sibling_hdr(hdr)) {
+				/* hdr->len includes CRLF */
+				append_str(p, hdr->name.s, hdr->len);
+				if (likely(hdr==reas_last))
+					break;
+			}
+		}
+	}
+#endif /* CANCEL_REASON_SUPPORT */
+	append_str(p, CRLF, CRLF_LEN); /* msg. end */
 	*p=0;
 
 	pkg_free(via);
@@ -217,7 +289,11 @@ error:
  * Can not be used to build other type of requests!
  */
 char *build_local_reparse(struct cell *Trans,unsigned int branch,
-	unsigned int *len, char *method, int method_len, str *to)
+	unsigned int *len, char *method, int method_len, str *to
+#ifdef CANCEL_REASON_SUPPORT
+	, struct cancel_reason *reason
+#endif /* CANCEL_REASON_SUPPORT */
+	)
 {
 	char	*invite_buf, *invite_buf_end;
 	char	*cancel_buf;
@@ -225,6 +301,11 @@ char *build_local_reparse(struct cell *Trans,unsigned int branch,
 	short	invite_len;
 	enum _hdr_types_t	hf_type;
 	int	first_via, to_len;
+	int cancel_buf_len;
+#ifdef CANCEL_REASON_SUPPORT
+	int reason_len, code_len;
+	struct hdr_field *reas1, *reas_last, *hdr;
+#endif /* CANCEL_REASON_SUPPORT */
 
 	invite_buf = Trans->uac[branch].request.buffer;
 	invite_len = Trans->uac[branch].request.buffer_len;
@@ -234,9 +315,43 @@ char *build_local_reparse(struct cell *Trans,unsigned int branch,
 		goto error;
 	}
 	if ((*invite_buf != 'I') && (*invite_buf != 'i')) {
-		LOG(L_ERR, "ERROR: build_local_reparse: trying to call build_local_reparse() for a non-INVITE request?\n");
+		LOG(L_ERR, "ERROR: trying to call build_local_reparse()"
+					" for a non-INVITE request?\n");
 		goto error;
 	}
+	
+#ifdef CANCEL_REASON_SUPPORT
+	reason_len = 0;
+	reas1 = 0;
+	reas_last = 0;
+	/* compute reason size (if no reason or disabled => reason_len == 0)*/
+	if (reason && reason->cause != CANCEL_REAS_UNKNOWN){
+		if (likely(reason->cause > 0 &&
+					cfg_get(tm, tm_cfg, local_cancel_reason))){
+			/* Reason: SIP;cause=<reason->cause>[;text=<reason->u.text.s>] */
+			reason_len = REASON_PREFIX_LEN + USHORT2SBUF_MAX_LEN +
+				(reason->u.text.s?
+					REASON_TEXT_LEN + 1 + reason->u.text.len + 1 : 0) +
+				CRLF_LEN;
+		} else if (likely(reason->cause == CANCEL_REAS_PACKED_HDRS &&
+					!(Trans->flags & T_NO_E2E_CANCEL_REASON))) {
+			reason_len = reason->u.packed_hdrs.len;
+		} else if (reason->cause == CANCEL_REAS_RCVD_CANCEL &&
+					reason->u.e2e_cancel &&
+					!(Trans->flags & T_NO_E2E_CANCEL_REASON)) {
+			/* parse the entire cancel, to get all the Reason headers */
+			parse_headers(reason->u.e2e_cancel, HDR_EOH_F, 0);
+			for(hdr=get_hdr(reason->u.e2e_cancel, HDR_REASON_T), reas1=hdr;
+					hdr; hdr=next_sibling_hdr(hdr)) {
+				/* hdr->len includes CRLF */
+				reason_len += hdr->len;
+				reas_last=hdr;
+			}
+		} else if (unlikely(reason->cause < CANCEL_REAS_MIN))
+			BUG("unhandled reason cause %d\n", reason->cause);
+	}
+#endif /* CANCEL_REASON_SUPPORT */
+
 	invite_buf_end = invite_buf + invite_len;
 	s = invite_buf;
 
@@ -245,10 +360,15 @@ char *build_local_reparse(struct cell *Trans,unsigned int branch,
 	I just extend it with the length of new To HF to be sure.
 	Ugly, but we avoid lots of checks and memory allocations this way */
 	to_len = to ? to->len : 0;
-	cancel_buf = shm_malloc(sizeof(char)*(invite_len + to_len));
+#ifdef CANCEL_REASON_SUPPORT
+	cancel_buf_len = invite_len + to_len + reason_len;
+#else
+	cancel_buf_len = invite_len + to_len;
+#endif /* CANCEL_REASON_SUPPORT */
+	cancel_buf = shm_malloc(sizeof(char)*cancel_buf_len);
 	if (!cancel_buf)
 	{
-		LOG(L_ERR, "ERROR: build_local_reparse: cannot allocate shared memory\n");
+		LOG(L_ERR, "ERROR: cannot allocate shared memory\n");
 		goto error;
 	}
 	d = cancel_buf;
@@ -281,7 +401,8 @@ char *build_local_reparse(struct cell *Trans,unsigned int branch,
 			case HDR_CSEQ_T:
 				/* find the method name and replace it */
 				while ((s < invite_buf_end)
-					&& ((*s == ':') || (*s == ' ') || (*s == '\t') || ((*s >= '0') && (*s <= '9')))
+					&& ((*s == ':') || (*s == ' ') || (*s == '\t') ||
+						((*s >= '0') && (*s <= '9')))
 					) s++;
 				append_str(d, s1, s - s1);
 				append_str(d, method, method_len);
@@ -300,7 +421,8 @@ char *build_local_reparse(struct cell *Trans,unsigned int branch,
 
 			case HDR_TO_T:
 				if (to_len == 0) {
-					/* there is no To tag required, just copy paste the header */
+					/* there is no To tag required, just copy paste
+					   the header */
 					s = lw_next_line(s, invite_buf_end);
 					append_str(d, s1, s - s1);
 				} else {
@@ -336,6 +458,41 @@ char *build_local_reparse(struct cell *Trans,unsigned int branch,
 
 			case HDR_EOH_T:
 				/* end of SIP message found */
+#ifdef CANCEL_REASON_SUPPORT
+				/* add reason if needed */
+				if (reason_len) {
+					/* if reason_len !=0, no need for any reason enabled
+					   checks */
+					if (likely(reason->cause > 0)) {
+						append_str(d, REASON_PREFIX, REASON_PREFIX_LEN);
+						code_len=ushort2sbuf(reason->cause, d,
+										cancel_buf_len-(int)(d-cancel_buf));
+						if (unlikely(code_len==0))
+							BUG("not enough space to write reason code");
+						d+=code_len;
+						if (reason->u.text.s){
+							append_str(d, REASON_TEXT, REASON_TEXT_LEN);
+							*d='"'; d++;
+							append_str(d, reason->u.text.s,
+											reason->u.text.len);
+							*d='"'; d++;
+						}
+						append_str(d, CRLF, CRLF_LEN);
+					} else if (likely(reason->cause ==
+										CANCEL_REAS_PACKED_HDRS)) {
+							append_str(d, reason->u.packed_hdrs.s,
+											reason->u.packed_hdrs.len);
+					} else if (reason->cause == CANCEL_REAS_RCVD_CANCEL) {
+						for(hdr=reas1; hdr; hdr=next_sibling_hdr(hdr)) {
+							/* hdr->len includes CRLF */
+							append_str(d, hdr->name.s, hdr->len);
+							if (likely(hdr==reas_last))
+								break;
+						}
+					}
+				}
+#endif /* CANCEL_REASON_SUPPORT */
+				/* final (end-of-headers) CRLF */
 				append_str(d, CRLF, CRLF_LEN);
 				*len = d - cancel_buf;
 				/* LOG(L_DBG, "DBG: build_local: %.*s\n", *len, cancel_buf); */

+ 11 - 2
modules/tm/t_msgbuilder.h

@@ -37,6 +37,7 @@
 #include "../../ip_addr.h"
 #include "defs.h"
 #include "dlg.h"
+#include "h_table.h"
 
 
 #define CSEQ "CSeq: "
@@ -54,10 +55,18 @@
 
 
 char *build_local(struct cell *Trans, unsigned int branch,
-	unsigned int *len, char *method, int method_len, str *to);
+	unsigned int *len, char *method, int method_len, str *to
+#ifdef CANCEL_REASON_SUPPORT
+	, struct cancel_reason* reason
+#endif /* CANCEL_REASON_SUPPORT */
+	);
 
 char *build_local_reparse(struct cell *Trans, unsigned int branch,
-	unsigned int *len, char *method, int method_len, str *to);
+	unsigned int *len, char *method, int method_len, str *to
+#ifdef CANCEL_REASON_SUPPORT
+	, struct cancel_reason* reason
+#endif /* CANCEL_REASON_SUPPORT */
+	);
 
 char *build_uac_request(  str msg_type, str dst, str from,
 	str fromtag, int cseq, str callid, str headers, 

+ 73 - 36
modules/tm/t_reply.c

@@ -96,6 +96,7 @@
  *             (andrei)
  * 2010-02-26  added experimental support for final reply dropping, not
  *             enabled by default (performance hit) (andrei)
+ * 2010-02-26  cancel reason (rfc3326) basic support (andrei)
  *
  */
 
@@ -401,11 +402,19 @@ static char *build_ack(struct sip_msg* rpl,struct cell *trans,int branch,
 	if (cfg_get(tm, tm_cfg, reparse_invite)) {
 		/* build the ACK from the INVITE which was sent out */
 		return build_local_reparse( trans, branch, ret_len,
-					ACK, ACK_LEN, &to );
+					ACK, ACK_LEN, &to
+	#ifdef CANCEL_REASON_SUPPORT
+					, 0
+	#endif /* CANCEL_REASON_SUPPORT */
+					);
 	} else {
 		/* build the ACK from the reveived INVITE */
 		return build_local( trans, branch, ret_len,
-					ACK, ACK_LEN, &to );
+					ACK, ACK_LEN, &to
+	#ifdef CANCEL_REASON_SUPPORT
+					, 0
+	#endif /* CANCEL_REASON_SUPPORT */
+					);
 	}
 }
 
@@ -546,23 +555,23 @@ static int _reply_light( struct cell *trans, char* buf, unsigned int len,
 {
 	struct retr_buf *rb;
 	unsigned int buf_len;
-	branch_bm_t cancel_bitmap;
+	struct cancel_info cancel_data;
 #ifdef TMCB_ONSEND
 	struct tmcb_params onsend_params;
 #endif
 
+	init_cancel_info(&cancel_data);
 	if (!buf)
 	{
 		DBG("DEBUG: _reply_light: response building failed\n");
 		/* determine if there are some branches to be canceled */
 		if ( is_invite(trans) ) {
-			prepare_to_cancel(trans, &cancel_bitmap, 0);
+			prepare_to_cancel(trans, &cancel_data.cancel_bitmap, 0);
 		}
 		/* and clean-up, including cancellations, if needed */
 		goto error;
 	}
 
-	cancel_bitmap=0;
 	if (lock) LOCK_REPLIES( trans );
 	if (trans->uas.status>=200) {
 		LOG( L_ERR, "ERROR: _reply_light: can't generate %d reply"
@@ -608,8 +617,11 @@ static int _reply_light( struct cell *trans, char* buf, unsigned int len,
 		}
 		cleanup_uac_timers( trans );
 		if (is_invite(trans)){
-			prepare_to_cancel(trans, &cancel_bitmap, 0);
-			cancel_uacs( trans, cancel_bitmap, F_CANCEL_B_KILL );
+			prepare_to_cancel(trans, &cancel_data.cancel_bitmap, 0);
+#ifdef CANCEL_REASON_SUPPORT
+			cancel_data.reason.cause=code;
+#endif /* CANCEL_REASON_SUPPORT */
+			cancel_uacs( trans, &cancel_data, F_CANCEL_B_KILL );
 		}
 		start_final_repl_retr(  trans );
 	}
@@ -658,15 +670,15 @@ static int _reply_light( struct cell *trans, char* buf, unsigned int len,
 	return 1;
 
 error3:
-	prepare_to_cancel(trans, &cancel_bitmap, 0);
+	prepare_to_cancel(trans, &cancel_data.cancel_bitmap, 0);
 error2:
 	if (lock) UNLOCK_REPLIES( trans );
 	pkg_free ( buf );
 error:
 	/* do UAC cleanup */
 	cleanup_uac_timers( trans );
-	if ( is_invite(trans) && cancel_bitmap )
-		cancel_uacs( trans, cancel_bitmap, F_CANCEL_B_KILL);
+	if ( is_invite(trans) && cancel_data.cancel_bitmap )
+		cancel_uacs( trans, &cancel_data, F_CANCEL_B_KILL);
 	/* we did not succeed -- put the transaction on wait */
 	put_on_wait(trans);
 	return -1;
@@ -1085,7 +1097,7 @@ static unsigned char drop_replies;
  */
 static enum rps t_should_relay_response( struct cell *Trans , int new_code,
 	int branch , int *should_store, int *should_relay,
-	branch_bm_t *cancel_bitmap, struct sip_msg *reply )
+	struct cancel_info *cancel_data, struct sip_msg *reply )
 {
 	int branch_cnt;
 	int picked_code;
@@ -1158,8 +1170,11 @@ static enum rps t_should_relay_response( struct cell *Trans , int new_code,
 				if (!(Trans->flags & (T_6xx | T_DISABLE_6xx))){
 					/* cancel only the first time we get a 6xx and only
 					  if the 6xx handling is not disabled */
-					prepare_to_cancel(Trans, cancel_bitmap, 0);
+					prepare_to_cancel(Trans, &cancel_data->cancel_bitmap, 0);
 					Trans->flags|=T_6xx;
+#ifdef CANCEL_REASON_SUPPORT
+					cancel_data->reason.cause=new_code;
+#endif /* CANCEL_REASON_SUPPORT */
 				}
 			}
 			return RPS_STORE;
@@ -1310,7 +1325,10 @@ static enum rps t_should_relay_response( struct cell *Trans , int new_code,
 		Trans->uac[branch].last_received=new_code;
 		*should_relay= new_code==100? -1 : branch;
 		if (new_code>=200 ) {
-			prepare_to_cancel( Trans, cancel_bitmap, 0);
+			prepare_to_cancel( Trans, &cancel_data->cancel_bitmap, 0);
+#ifdef CANCEL_REASON_SUPPORT
+			cancel_data->reason.cause=new_code;
+#endif /* CANCEL_REASON_SUPPORT */
 			return RPS_COMPLETED;
 		} else return RPS_PROVISIONAL;
 	}
@@ -1553,7 +1571,8 @@ skip:
    wait timer will be started (put_on_wait(t)).
 */
 enum rps relay_reply( struct cell *t, struct sip_msg *p_msg, int branch,
-	unsigned int msg_status, branch_bm_t *cancel_bitmap, int do_put_on_wait )
+	unsigned int msg_status, struct cancel_info *cancel_data,
+	int do_put_on_wait )
 {
 	int relay;
 	int save_clone;
@@ -1586,7 +1605,7 @@ enum rps relay_reply( struct cell *t, struct sip_msg *p_msg, int branch,
 
 	/* *** store and relay message as needed *** */
 	reply_status = t_should_relay_response(t, msg_status, branch,
-		&save_clone, &relay, cancel_bitmap, p_msg );
+		&save_clone, &relay, cancel_data, p_msg );
 	DBG("DEBUG: relay_reply: branch=%d, save=%d, relay=%d\n",
 		branch, save_clone, relay );
 
@@ -1802,7 +1821,8 @@ error02:
 	}
 error01:
 	t_reply_unsafe( t, t->uas.request, 500, "Reply processing error" );
-	*cancel_bitmap=0; /* t_reply_unsafe already canceled everything needed */
+	cancel_data->cancel_bitmap=0; /* t_reply_unsafe already canceled
+									 everything needed */
 	UNLOCK_REPLIES(t);
 	/* if (is_invite(t)) cancel_uacs( t, *cancel_bitmap, 0); 
 	 *  -- not needed, t_reply_unsafe took care of this */
@@ -1819,7 +1839,7 @@ error01:
    it is entered locked with REPLY_LOCK and it returns unlocked!
 */
 enum rps local_reply( struct cell *t, struct sip_msg *p_msg, int branch,
-	unsigned int msg_status, branch_bm_t *cancel_bitmap)
+	unsigned int msg_status, struct cancel_info *cancel_data)
 {
 	/* how to deal with replies for local transaction */
 	int local_store, local_winner;
@@ -1827,17 +1847,16 @@ enum rps local_reply( struct cell *t, struct sip_msg *p_msg, int branch,
 	struct sip_msg *winning_msg;
 	int winning_code;
 	int totag_retr;
-	/* branch_bm_t cancel_bitmap; */
 
 	/* keep warning 'var might be used un-inited' silent */
 	winning_msg=0;
 	winning_code=0;
 	totag_retr=0;
 
-	*cancel_bitmap=0;
+	cancel_data->cancel_bitmap=0;
 
 	reply_status=t_should_relay_response( t, msg_status, branch,
-		&local_store, &local_winner, cancel_bitmap, p_msg );
+		&local_store, &local_winner, cancel_data, p_msg );
 	DBG("DEBUG: local_reply: branch=%d, save=%d, winner=%d\n",
 		branch, local_store, local_winner );
 	if (local_store) {
@@ -1886,13 +1905,14 @@ enum rps local_reply( struct cell *t, struct sip_msg *p_msg, int branch,
 	return reply_status;
 
 error:
-	prepare_to_cancel(t, cancel_bitmap, 0);
+	prepare_to_cancel(t, &cancel_data->cancel_bitmap, 0);
 	UNLOCK_REPLIES(t);
 	cleanup_uac_timers(t);
 	if (p_msg && p_msg!=FAKED_REPLY && get_cseq(p_msg)->method.len==INVITE_LEN
-		&& memcmp( get_cseq(p_msg)->method.s, INVITE, INVITE_LEN)==0)
-		cancel_uacs( t, *cancel_bitmap, F_CANCEL_B_KILL);
-	*cancel_bitmap=0; /* we've already took care of everything */
+		&& memcmp( get_cseq(p_msg)->method.s, INVITE, INVITE_LEN)==0){
+		cancel_uacs( t, cancel_data, F_CANCEL_B_KILL);
+	}
+	cancel_data->cancel_bitmap=0; /* we've already took care of everything */
 	put_on_wait(t);
 	return RPS_ERROR;
 }
@@ -1917,7 +1937,7 @@ int reply_received( struct sip_msg  *p_msg )
 	/* has the transaction completed now and we need to clean-up? */
 	int reply_status;
 	int onreply_route;
-	branch_bm_t cancel_bitmap;
+	struct cancel_info cancel_data;
 	struct ua_client *uac;
 	struct cell *t;
 	struct dest_info  lack_dst;
@@ -1952,7 +1972,7 @@ int reply_received( struct sip_msg  *p_msg )
 	if (unlikely(branch==T_BR_UNDEFINED))
 		BUG("invalid branch, please report to [email protected]\n");
 	tm_ctx_set_branch_index(branch);
-	cancel_bitmap=0;
+	init_cancel_info(&cancel_data);
 	msg_status=p_msg->REPLY_STATUS;
 	replies_locked=0;
 
@@ -2053,13 +2073,30 @@ int reply_received( struct sip_msg  *p_msg )
 				SEND_BUFFER( &uac->local_cancel );
 #endif
 				/* retrs. should be already started so do nothing */
-			}else if (atomic_cmpxchg_long((void*)&uac->local_cancel.buffer, 0, 
+			}else if (atomic_cmpxchg_long((void*)&uac->local_cancel.buffer, 0,
 										(long)BUSY_BUFFER)==0){
 				/* try to rebuild it if empty (not set or marked as BUSY).
 				 * if BUSY or set just exit, a cancel will be (or was) sent 
 				 * shortly on this branch */
 				DBG("tm: reply_received: branch CANCEL created\n");
+#ifdef CANCEL_REASON_SUPPORT
+				if (t->uas.cancel_reas) {
+					/* cancel reason was saved, use it */
+					cancel_branch(t, branch, t->uas.cancel_reas,
+														F_CANCEL_B_FORCE_C);
+				} else {
+					/* note that in this case we do not know the reason,
+					   we only know it's a final reply (either locally
+					   generated via script t_reply(), timeout, a received
+					   2xx or 6xx) => try to use t->uas.status as the reason*/
+					cancel_data.reason.cause =
+						(t->uas.status>=200)?t->uas.status:CANCEL_REAS_UNKNOWN;
+					cancel_branch(t, branch, &cancel_data.reason,
+														F_CANCEL_B_FORCE_C);
+				}
+#else /* CANCEL_REASON_SUPPORT */
 				cancel_branch(t, branch, F_CANCEL_B_FORCE_C);
+#endif /* CANCEL_REASON_SUPPORT */
 			}
 			goto done; /* nothing to do */
 		}
@@ -2214,24 +2251,24 @@ int reply_received( struct sip_msg  *p_msg )
 		replies_locked=1;
 	}
 	if ( is_local(t) ) {
-		reply_status=local_reply( t, p_msg, branch, msg_status, &cancel_bitmap );
+		reply_status=local_reply( t, p_msg, branch, msg_status, &cancel_data );
 		if (reply_status == RPS_COMPLETED) {
 			     /* no more UAC FR/RETR (if I received a 2xx, there may
 			      * be still pending branches ...
 			      */
 			cleanup_uac_timers( t );
-			if (is_invite(t)) cancel_uacs(t, cancel_bitmap, F_CANCEL_B_KILL);
+			if (is_invite(t)) cancel_uacs(t, &cancel_data, F_CANCEL_B_KILL);
 			/* There is no need to call set_final_timer because we know
 			 * that the transaction is local */
 			put_on_wait(t);
-		}else if (cancel_bitmap){
+		}else if (unlikely(cancel_data.cancel_bitmap)){
 			/* cancel everything, even non-INVITEs (e.g in case of 6xx), use
 			 * cancel_b_method for canceling unreplied branches */
-			cancel_uacs(t, cancel_bitmap, cfg_get(tm,tm_cfg, cancel_b_flags));
+			cancel_uacs(t, &cancel_data, cfg_get(tm,tm_cfg, cancel_b_flags));
 		}
 	} else {
 		reply_status=relay_reply( t, p_msg, branch, msg_status,
-									&cancel_bitmap, 1 );
+									&cancel_data, 1 );
 		if (reply_status == RPS_COMPLETED) {
 			     /* no more UAC FR/RETR (if I received a 2xx, there may
 				be still pending branches ...
@@ -2239,16 +2276,16 @@ int reply_received( struct sip_msg  *p_msg )
 			cleanup_uac_timers( t );
 			/* 2xx is a special case: we can have a COMPLETED request
 			 * with branches still open => we have to cancel them */
-			if (is_invite(t) && cancel_bitmap) 
-				cancel_uacs( t, cancel_bitmap,  F_CANCEL_B_KILL);
+			if (is_invite(t) && cancel_data.cancel_bitmap) 
+				cancel_uacs( t, &cancel_data,  F_CANCEL_B_KILL);
 			/* FR for negative INVITES, WAIT anything else */
 			/* Call to set_final_timer is embedded in relay_reply to avoid
 			 * race conditions when reply is sent out and an ACK to stop
 			 * retransmissions comes before retransmission timer is set.*/
-		}else if (cancel_bitmap){
+		}else if (unlikely(cancel_data.cancel_bitmap)){
 			/* cancel everything, even non-INVITEs (e.g in case of 6xx), use
 			 * cancel_b_method for canceling unreplied branches */
-			cancel_uacs(t, cancel_bitmap, cfg_get(tm,tm_cfg, cancel_b_flags));
+			cancel_uacs(t, &cancel_data, cfg_get(tm,tm_cfg, cancel_b_flags));
 		}
 	}
 	uac->request.flags|=F_RB_REPLIED;

+ 66 - 3
modules/tm/t_reply.h

@@ -30,6 +30,11 @@
 #ifndef _T_REPLY_H
 #define _T_REPLY_H
 
+/* CANCEL_REASON_SUPPORT on by default */
+#ifndef NO_CANCEL_REASON_SUPPORT
+#define CANCEL_REASON_SUPPORT
+#endif /* NO_CANCEL_REASON_SUPPORT */
+
 #include "defs.h"
 #include "../../rpc.h"
 #include "../../tags.h"
@@ -66,6 +71,63 @@ int unmatched_totag(struct cell *t, struct sip_msg *ack);
 /* branch bitmap type */
 typedef unsigned int branch_bm_t;
 
+#ifdef CANCEL_REASON_SUPPORT
+
+/* reason building blocks (see rfc3326) */
+#define REASON_PREFIX "Reason: SIP;cause="
+#define REASON_PREFIX_LEN (sizeof(REASON_PREFIX)-1)
+#define REASON_TEXT ";text="
+#define REASON_TEXT_LEN (sizeof(REASON_TEXT)-1)
+
+#define CANCEL_REAS_UNKNOWN 0
+#define CANCEL_REAS_PACKED_HDRS -1
+#define CANCEL_REAS_RCVD_CANCEL -2
+#define CANCEL_REAS_FINAL_REPLY(x) (x)
+#define CANCEL_REAS_MIN CANCEL_REAS_RCVD_CANCEL
+
+
+/** cancel reason structure.*/
+struct cancel_reason {
+	short cause; /**< 0 = unknown, -1 =  cancel, > 0 final reply code. */
+	union{
+		str text; /**< reason text if reason is final reply .*/
+		struct sip_msg* e2e_cancel; /**< cancel msg if reason is cancel. */
+		str packed_hdrs; /**< complete reason headers. */
+	}u;
+};
+
+struct cancel_info {
+	branch_bm_t cancel_bitmap; /**< cancel branch bitmap */
+	struct cancel_reason reason;
+};
+
+
+#define init_cancel_reason(cr) \
+	do {\
+		(cr)->cause=0; \
+		(cr)->u.e2e_cancel=0; \
+	} while(0)
+
+#define init_cancel_info(ci) \
+	do {\
+		(ci)->cancel_bitmap=0; \
+		init_cancel_reason(&(ci)->reason); \
+	}while (0);
+
+#else /* ! CANCEL_REASON_SUPPORT */
+
+struct cancel_info {
+	branch_bm_t cancel_bitmap; /**< cancel branch bitmap */
+};
+
+#define init_cancel_info(ci) \
+	do {\
+		(ci)->cancel_bitmap=0; \
+	}while (0);
+
+#endif /* CANCEL_REASON_SUPPORT */
+
+
 /* reply export types */
 typedef int (*treply_f)(struct sip_msg * , unsigned int , char * );
 typedef int (*treply_wb_f)( struct cell* trans,
@@ -124,11 +186,12 @@ int t_reply( struct cell *t, struct sip_msg * , unsigned int , char * );
 int t_reply_unsafe( struct cell *t, struct sip_msg * , unsigned int , char * );
 
 
-enum rps relay_reply( struct cell *t, struct sip_msg *p_msg, int branch, 
-	unsigned int msg_status, branch_bm_t *cancel_bitmap, int do_put_on_wait );
+enum rps relay_reply( struct cell *t, struct sip_msg *p_msg, int branch,
+	unsigned int msg_status, struct cancel_info *cancel_data,
+	int do_put_on_wait );
 
 enum rps local_reply( struct cell *t, struct sip_msg *p_msg, int branch,
-    unsigned int msg_status, branch_bm_t *cancel_bitmap );
+	unsigned int msg_status, struct cancel_info *cancel_data );
 
 void set_final_timer( /* struct s_table *h_table,*/ struct cell *t );
 

+ 7 - 3
modules/tm/timer.c

@@ -299,7 +299,7 @@ inline static ticks_t  delete_cell( struct cell *p_cell, int unlock )
  * it assumes the REPLY_LOCK is already held and returns unlocked */
 static void fake_reply(struct cell *t, int branch, int code )
 {
-	branch_bm_t cancel_bitmap;
+	struct cancel_info cancel_data;
 	short do_cancel_branch;
 	enum rps reply_status;
 
@@ -308,15 +308,19 @@ static void fake_reply(struct cell *t, int branch, int code )
 	t->uac[branch].request.flags|=F_RB_CANCELED;
 	if ( is_local(t) ) {
 		reply_status=local_reply( t, FAKED_REPLY, branch, 
-					  code, &cancel_bitmap );
+					  code, &cancel_data );
 	} else {
 		/* rely reply, but don't put on wait, we still need t
 		 * to send the cancels */
 		reply_status=relay_reply( t, FAKED_REPLY, branch, code,
-					  &cancel_bitmap, 0 );
+					  &cancel_data, 0 );
 	}
 	/* now when out-of-lock do the cancel I/O */
+#ifdef CANCEL_REASON_SUPPORT
+	if (do_cancel_branch) cancel_branch(t, branch, &cancel_data.reason, 0);
+#else /* CANCEL_REASON_SUPPORT */
 	if (do_cancel_branch) cancel_branch(t, branch, 0);
+#endif /* CANCEL_REASON_SUPPORT */
 	/* it's cleaned up on error; if no error occurred and transaction
 	   completed regularly, I have to clean-up myself
 	*/

+ 29 - 6
modules/tm/tm.c

@@ -87,16 +87,17 @@
  *  2008-08-11  sctp support: t_relay_to_sctp, t_replicate_sctp,
  *               t_forward_nonack_sctp (andrei)
  *  2009-03-18  added a new param: auto_inv_100_reason (aheise) 
+ *  2010-03-03  added new params: local_cancel_reason and e2e_cancel_reason
+ *              (andrei)
  */
 
-/*!
- * \file 
- * \brief TM :: Module API (core)
- * \ingroup tm
+/** TM :: Module API (core).
+ * @file 
+ * @ingroup tm
  */
 
-/*!
- * \defgroup tm TM :: Transaction stateful proxy support
+/**
+ * @defgroup tm TM :: Transaction stateful proxy support
  *
    The TM module enables stateful processing of SIP transactions. The main use
    of stateful logic, which is costly in terms of memory and CPU, is some
@@ -283,6 +284,10 @@ static int w_t_reset_max_lifetime(struct sip_msg* msg, char* foo, char* bar);
 static int t_set_auto_inv_100(struct sip_msg* msg, char* on_off, char* foo);
 static int t_set_disable_6xx(struct sip_msg* msg, char* on_off, char* foo);
 static int t_set_disable_failover(struct sip_msg* msg, char* on_off, char* f);
+#ifdef CANCEL_REASON_SUPPORT
+static int t_set_no_e2e_cancel_reason(struct sip_msg* msg, char* on_off,
+										char* f);
+#endif /* CANCEL_REASON_SUPPORT */
 static int t_branch_timeout(struct sip_msg* msg, char*, char*);
 static int t_branch_replied(struct sip_msg* msg, char*, char*);
 static int t_any_timeout(struct sip_msg* msg, char*, char*);
@@ -428,6 +433,15 @@ static cmd_export_t cmds[]={
 			REQUEST_ROUTE|TM_ONREPLY_ROUTE|FAILURE_ROUTE|BRANCH_ROUTE },
 	{"t_set_disable_failover", t_set_disable_failover, 1, fixup_var_int_1,
 			REQUEST_ROUTE|TM_ONREPLY_ROUTE|FAILURE_ROUTE|BRANCH_ROUTE },
+#ifdef CANCEL_REASON_SUPPORT
+	{"t_set_no_e2e_cancel_reason", t_set_no_e2e_cancel_reason, 1,
+		fixup_var_int_1,
+			REQUEST_ROUTE|TM_ONREPLY_ROUTE|FAILURE_ROUTE|BRANCH_ROUTE },
+	/* alias for t_set_no_e2e_cancel_reason */
+	{"t_disable_e2e_cancel_reason", t_set_no_e2e_cancel_reason, 1,
+		fixup_var_int_1,
+			REQUEST_ROUTE|TM_ONREPLY_ROUTE|FAILURE_ROUTE|BRANCH_ROUTE },
+#endif /* CANCEL_REASON_SUPPORT */
 	{"t_branch_timeout",  t_branch_timeout,         0, 0,  FAILURE_ROUTE},
 	{"t_branch_replied",  t_branch_replied,         0, 0,  FAILURE_ROUTE},
 	{"t_any_timeout",     t_any_timeout,            0, 0, 
@@ -502,6 +516,10 @@ static param_export_t params[]={
 	{"disable_6xx_block",   PARAM_INT, &default_tm_cfg.disable_6xx           },
 	{"local_ack_mode",      PARAM_INT, &default_tm_cfg.local_ack_mode        },
 	{"failure_reply_mode",  PARAM_INT, &failure_reply_mode                   },
+#ifdef CANCEL_REASON_SUPPORT
+	{"local_cancel_reason", PARAM_INT, &default_tm_cfg.local_cancel_reason   },
+	{"e2e_cancel_reason",   PARAM_INT, &default_tm_cfg.e2e_cancel_reason     },
+#endif /* CANCEL_REASON_SUPPORT */
 	{0,0,0}
 };
 
@@ -1711,6 +1729,11 @@ T_SET_FLAG_GEN_FUNC(t_set_disable_6xx, T_DISABLE_6xx)
 T_SET_FLAG_GEN_FUNC(t_set_disable_failover, T_DISABLE_FAILOVER)
 
 
+#ifdef CANCEL_REASON_SUPPORT
+/* disable/enable e2e cancel reason copy for the current transaction */
+T_SET_FLAG_GEN_FUNC(t_set_no_e2e_cancel_reason, T_NO_E2E_CANCEL_REASON)
+#endif /* CANCEL_REASON_SUPPORT */
+
 
 /* script function, FAILURE_ROUTE only, returns true if the 
  * choosed "failure" branch failed because of a timeout,