浏览代码

tm: improved Reason support for E2E CANCELs

When generating CANCELs on-the-fly as response to a received
provisional reply on a branch of an E2E CANCELed transaction,
remember the Reason(s) from the original E2E CANCEL.
Up until now, the Reason was not remembered for branches where no
provisional replies were received (on these branches no CANCEL is
sent, unless cancel_b_method is set to 2 or at a later point a
provisional reply arrives).
Now each time an E2E CANCEL is received the Reason headers are
saved inside the INVITE transaction, for possible use in later
branch CANCEL generation (triggered by receiving a provisional
reply).
Andrei Pelinescu-Onciul 15 年之前
父节点
当前提交
c96405e486
共有 6 个文件被更改,包括 160 次插入23 次删除
  1. 2 0
      modules/tm/h_table.c
  2. 2 0
      modules/tm/h_table.h
  3. 118 5
      modules/tm/t_fwd.c
  4. 14 8
      modules/tm/t_msgbuilder.c
  5. 13 9
      modules/tm/t_reply.c
  6. 11 1
      modules/tm/t_reply.h

+ 2 - 0
modules/tm/h_table.c

@@ -138,6 +138,8 @@ 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 );
+	if (unlikely(dead_cell->uas.cancel_reas))
+		shm_free_unsafe(dead_cell->uas.cancel_reas);
 
 	/* callbacks */
 	for( cbs=(struct tm_callback*)dead_cell->tmcb_hl.first ; cbs ; ) {

+ 2 - 0
modules/tm/h_table.h

@@ -191,6 +191,8 @@ typedef struct ua_server
 	 * we need them for dialog-wise matching of ACKs;
 	 * the pointer shows to shmem-ed reply */
 	str				 local_totag;
+	struct cancel_reason* cancel_reas; /* pointer to cancel reason, used
+										  for e2e cancels */
 	unsigned int     status;
 }ua_server_type;
 

+ 118 - 5
modules/tm/t_fwd.c

@@ -951,6 +951,104 @@ error:
 
 
 
+/** 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;
+		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;
+}
+
+
+
 void e2e_cancel( struct sip_msg *cancel_msg,
 	struct cell *t_cancel, struct cell *t_invite )
 {
@@ -958,7 +1056,8 @@ void e2e_cancel( struct sip_msg *cancel_msg,
 #ifndef E2E_CANCEL_HOP_BY_HOP
 	branch_bm_t tmp_bm;
 #else /* def E2E_CANCEL_HOP_BY_HOP */
-	struct cancel_reason reason;
+	struct cancel_reason* reason;
+	int free_reason;
 #endif /* E2E_CANCEL_HOP_BY_HOP */
 	int i;
 	int lowest_error;
@@ -1004,9 +1103,19 @@ void e2e_cancel( struct sip_msg *cancel_msg,
 	 * have 0 branches and we check for the branch number in 
 	 * t_reply_matching() ).
 	 */
-	init_cancel_reason(&reason);
-	reason.cause=CANCEL_REAS_RCVD_CANCEL;
-	reason.u.e2e_cancel=cancel_msg;
+	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;
+		}
+	}
 	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
@@ -1016,7 +1125,7 @@ void e2e_cancel( struct sip_msg *cancel_msg,
 			ret=cancel_branch(
 				t_invite,
 				i,
-				&reason,
+				reason,
 				cfg_get(tm,tm_cfg, cancel_b_flags)
 					| ((t_invite->uac[i].request.buffer==NULL)?
 						F_CANCEL_B_FAKE_REPLY:0) /* blind UAC? */
@@ -1024,6 +1133,10 @@ void e2e_cancel( struct sip_msg *cancel_msg,
 			if (ret<0) cancel_bm &= ~(1<<i);
 			if (ret<lowest_error) lowest_error=ret;
 		}
+	if (unlikely(free_reason)) {
+		/* reason was not set as the global reason => free it */
+		shm_free(reason);
+	}
 #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

+ 14 - 8
modules/tm/t_msgbuilder.c

@@ -74,12 +74,6 @@
 #endif
 
 
-/* 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)
-
 /* convenience macros */
 #define memapp(_d,_s,_len) \
 	do{\
@@ -181,6 +175,9 @@ char *build_local(struct cell *Trans,unsigned int branch,
 				(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)) {
@@ -192,7 +189,7 @@ char *build_local(struct cell *Trans,unsigned int branch,
 				reason_len += hdr->len;
 				reas_last=hdr;
 			}
-		} else if (unlikely(reason->cause < -1))
+		} else if (unlikely(reason->cause < CANCEL_REAS_MIN))
 			BUG("unhandled reason cause %d\n", reason->cause);
 	}
 	*len+= reason_len;
@@ -254,6 +251,8 @@ char *build_local(struct cell *Trans,unsigned int branch,
 				*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 */
@@ -318,6 +317,9 @@ char *build_local_reparse(struct cell *Trans,unsigned int branch,
 				(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)) {
@@ -329,7 +331,7 @@ char *build_local_reparse(struct cell *Trans,unsigned int branch,
 				reason_len += hdr->len;
 				reas_last=hdr;
 			}
-		} else if (unlikely(reason->cause < -1))
+		} else if (unlikely(reason->cause < CANCEL_REAS_MIN))
 			BUG("unhandled reason cause %d\n", reason->cause);
 	}
 
@@ -454,6 +456,10 @@ char *build_local_reparse(struct cell *Trans,unsigned int branch,
 							*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 */

+ 13 - 9
modules/tm/t_reply.c

@@ -2041,16 +2041,20 @@ int reply_received( struct sip_msg  *p_msg )
 				 * 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");
-				/* note that in this case we do not know the reason
-				   (it could be a final reply or a received cancel)
-				   and we don't want to wait for it. However if
-				   t->uas.status >= 200 it's probably due to a received
-				   2xx, 6xx, local timeout or a local final reply 
-				   (via t_reply()), so use t->uas.status as reason */
-				cancel_data.reason.cause = (t->uas.status>=200)?t->uas.status:
-											CANCEL_REAS_UNKNOWN;
-				cancel_branch(t, branch, &cancel_data.reason,
+				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);
+				}
 			}
 			goto done; /* nothing to do */
 		}

+ 11 - 1
modules/tm/t_reply.h

@@ -65,9 +65,18 @@ int unmatched_totag(struct cell *t, struct sip_msg *ack);
 typedef unsigned int branch_bm_t;
 
 
+/* 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_RCVD_CANCEL -1
+#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 {
@@ -75,6 +84,7 @@ struct cancel_reason {
 	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;
 };