Browse Source

When building the route set of ACKs for local UACs, only the reply is now
evaluated; this is insufficient, since replies to in-dialog requests normally
miss the route set. The patch fixes that: evals INVITE, if this was in-dialog;
otherwise, the reply (since the req. doesn't have yet complete route set).

Add support for the SASI if'ace, adding the implemenation for two more TM API
functions:
- t_get_canceled_ident(): returns the hash coordinates (bucket/index) of the
transaction the currently processed CANCEL is targeting
- ack_local_uac(): allow generating the ACKs for 2xx'ed locally originated
INVITEs - new headers and body can now also be appended to it.

Fully closes #SER-346.

Bogdan Pintea 16 years ago
parent
commit
d65cdd3fd4
12 changed files with 817 additions and 117 deletions
  1. 3 0
      Makefile.defs
  2. 7 1
      NEWS
  3. 19 7
      modules/tm/dlg.c
  4. 4 0
      modules/tm/h_table.c
  5. 23 2
      modules/tm/h_table.h
  6. 18 4
      modules/tm/t_hooks.h
  7. 30 1
      modules/tm/t_lookup.c
  8. 504 77
      modules/tm/t_msgbuilder.c
  9. 2 2
      modules/tm/t_msgbuilder.h
  10. 48 9
      modules/tm/t_reply.c
  11. 143 14
      modules/tm/uac.c
  12. 16 0
      modules/tm/uac.h

+ 3 - 0
Makefile.defs

@@ -456,6 +456,8 @@ endif
 #		core that the DNS servers are down. No DNS query is performed
 #		core that the DNS servers are down. No DNS query is performed
 #		when the servers are unreachable, and even expired resource
 #		when the servers are unreachable, and even expired resource
 #		records are used from the cache. (requires external watchdog)
 #		records are used from the cache. (requires external watchdog)
+# -DWITH_AS_SUPPORT
+#		adds support for Application Server interface
 # Sometimes is needes correct non-quoted $OS. HACK: gcc translates known OS to number ('linux'), so there is added underscore
 # Sometimes is needes correct non-quoted $OS. HACK: gcc translates known OS to number ('linux'), so there is added underscore
 
 
 DEFS= $(extra_defs) \
 DEFS= $(extra_defs) \
@@ -476,6 +478,7 @@ DEFS= $(extra_defs) \
 	 -DUSE_DNS_FAILOVER \
 	 -DUSE_DNS_FAILOVER \
 	 -DUSE_DST_BLACKLIST \
 	 -DUSE_DST_BLACKLIST \
 	 -DUSE_NAPTR \
 	 -DUSE_NAPTR \
+	 -DWITH_AS_SUPPORT \
 	 -DDBG_QM_MALLOC \
 	 -DDBG_QM_MALLOC \
 	 #-DUSE_DNS_CACHE_STATS \
 	 #-DUSE_DNS_CACHE_STATS \
 	 #-DUSE_DST_BLACKLIST_STATS \
 	 #-DUSE_DST_BLACKLIST_STATS \

+ 7 - 1
NEWS

@@ -77,7 +77,13 @@ modules:
  - blst      - new module containing script blacklist manipulations functions
  - blst      - new module containing script blacklist manipulations functions
                (the source of a message can be blacklisted, removed from the
                (the source of a message can be blacklisted, removed from the
                 blacklist or checked for presence in the blacklist).
                 blacklist or checked for presence in the blacklist).
- - tm        - matching of E2E ACKs no longer requires full From HF identity,
+ - tm        - added API function t_get_canceled_ident(): returns the hash 
+               coordinates (bucket/index) of the transaction the currently 
+               processed CANCEL is targeting. Requires AS support enabled.
+             - added API function ack_local_uac(): allow generating the ACKs 
+               for 2xx'ed locally originated INVITEs - new headers and body can
+               now also be appended to it. Requires AS support enabled.
+             - matching of E2E ACKs no longer requires full From HF identity,
                but rather only tag equality (this behaviour can be changed by
                but rather only tag equality (this behaviour can be changed by
                defining TM_E2E_ACK_CHECK_FROM_URI)
                defining TM_E2E_ACK_CHECK_FROM_URI)
              - added t_reset_fr(), t_reset_retr(), t_reset_max_lifetime()
              - added t_reset_fr(), t_reset_retr(), t_reset_max_lifetime()

+ 19 - 7
modules/tm/dlg.c

@@ -215,11 +215,17 @@ static inline int str_duplicate(str* _d, str* _s)
 
 
 /*
 /*
  * Calculate dialog hooks
  * Calculate dialog hooks
+ * @return:
+ *  negative : error
+ *  0 : no routes present
+ *  F_RB_NH_LOOSE : routes present, next hop is loose router
+ *  F_RB_NH_STRICT: next hop is strict.
  */
  */
 static inline int calculate_hooks(dlg_t* _d)
 static inline int calculate_hooks(dlg_t* _d)
 {
 {
 	str* uri;
 	str* uri;
 	struct sip_uri puri;
 	struct sip_uri puri;
+	int nhop;
 
 
 	/* we might re-calc. some existing hooks =>
 	/* we might re-calc. some existing hooks =>
 	 * reset all the hooks to 0 */
 	 * reset all the hooks to 0 */
@@ -236,6 +242,7 @@ static inline int calculate_hooks(dlg_t* _d)
 			else _d->hooks.request_uri = &_d->rem_uri;
 			else _d->hooks.request_uri = &_d->rem_uri;
 			_d->hooks.next_hop = &_d->route_set->nameaddr.uri;
 			_d->hooks.next_hop = &_d->route_set->nameaddr.uri;
 			_d->hooks.first_route = _d->route_set;
 			_d->hooks.first_route = _d->route_set;
+			nhop = F_RB_NH_LOOSE;
 		} else {
 		} else {
 			_d->hooks.request_uri = &_d->route_set->nameaddr.uri;
 			_d->hooks.request_uri = &_d->route_set->nameaddr.uri;
 			_d->hooks.next_hop = _d->hooks.request_uri;
 			_d->hooks.next_hop = _d->hooks.request_uri;
@@ -244,6 +251,7 @@ static inline int calculate_hooks(dlg_t* _d)
 				_d->hooks.last_route = &_d->rem_target;
 				_d->hooks.last_route = &_d->rem_target;
 			else 
 			else 
 				_d->hooks.last_route = NULL; /* ? */
 				_d->hooks.last_route = NULL; /* ? */
+			nhop = F_RB_NH_STRICT;
 		}
 		}
 	} else {
 	} else {
 		if (_d->rem_target.s) _d->hooks.request_uri = &_d->rem_target;
 		if (_d->rem_target.s) _d->hooks.request_uri = &_d->rem_target;
@@ -252,12 +260,14 @@ static inline int calculate_hooks(dlg_t* _d)
 		if (_d->dst_uri.s) _d->hooks.next_hop = &_d->dst_uri;
 		if (_d->dst_uri.s) _d->hooks.next_hop = &_d->dst_uri;
 		else _d->hooks.next_hop = _d->hooks.request_uri;
 		else _d->hooks.next_hop = _d->hooks.request_uri;
 
 
+		nhop = 0;
 		/*
 		/*
-		 * the routes in the hooks need to be reset because if the route_set was dropped somewhere else
-		 * then these will remain set without the actual routes existing any more
+		 * the routes in the hooks need to be reset because if the route_set 
+		 * was dropped somewhere else then these will remain set without the
+		 * actual routes existing any more
 		 */
 		 */
 		_d->hooks.first_route = 0;
 		_d->hooks.first_route = 0;
-		_d->hooks.last_route = 0; 
+		_d->hooks.last_route = 0;
 	}
 	}
 
 
 	if ((_d->hooks.request_uri) && (_d->hooks.request_uri->s) && (_d->hooks.request_uri->len)) {
 	if ((_d->hooks.request_uri) && (_d->hooks.request_uri->s) && (_d->hooks.request_uri->len)) {
@@ -273,7 +283,7 @@ static inline int calculate_hooks(dlg_t* _d)
 		get_raw_uri(_d->hooks.next_hop);
 		get_raw_uri(_d->hooks.next_hop);
 	}
 	}
 
 
-	return 0;
+	return nhop;
 }
 }
 
 
 /*
 /*
@@ -738,7 +748,8 @@ static inline int dlg_confirmed_resp_uac(dlg_t* _d, struct sip_msg* _m,
 			if (str_duplicate(&_d->rem_target, &contact) < 0) return -4;
 			if (str_duplicate(&_d->rem_target, &contact) < 0) return -4;
 		}
 		}
 
 
-		calculate_hooks(_d);
+		if (calculate_hooks(_d) < 0)
+			return -1;
 	}
 	}
 
 
 	return 0;
 	return 0;
@@ -1078,7 +1089,8 @@ int dlg_request_uas(dlg_t* _d, struct sip_msg* _m, target_refresh_t is_target_re
 			if (str_duplicate(&_d->rem_target, &contact) < 0) return -6;
 			if (str_duplicate(&_d->rem_target, &contact) < 0) return -6;
 		}
 		}
 
 
-		calculate_hooks(_d);
+		if (calculate_hooks(_d) < 0)
+			return -1;
 		
 		
 	}
 	}
 
 
@@ -1248,7 +1260,7 @@ int set_dlg_target(dlg_t* _d, str* _ruri, str* _duri) {
 		if (str_duplicate(&_d->dst_uri, _duri)) return -1;
 		if (str_duplicate(&_d->dst_uri, _duri)) return -1;
 	}
 	}
 
 
-	if (calculate_hooks(_d)) {
+	if (calculate_hooks(_d) < 0) {
 		LOG(L_ERR, "set_dlg_target(): Error while calculating hooks\n");
 		LOG(L_ERR, "set_dlg_target(): Error while calculating hooks\n");
 		return -1;
 		return -1;
 	}
 	}

+ 4 - 0
modules/tm/h_table.c

@@ -65,6 +65,7 @@
 #include "h_table.h"
 #include "h_table.h"
 #include "fix_lumps.h" /* free_via_clen_lump */
 #include "fix_lumps.h" /* free_via_clen_lump */
 #include "timer.h"
 #include "timer.h"
+#include "uac.h" /* free_local_ack */
 
 
 
 
 static enum kill_reason kr;
 static enum kill_reason kr;
@@ -174,6 +175,9 @@ void free_cell( struct cell* dead_cell )
 #endif
 #endif
 	}
 	}
 
 
+	if (dead_cell->uac[0].local_ack)
+		free_local_ack_unsafe(dead_cell->uac[0].local_ack);
+
 	/* collected to tags */
 	/* collected to tags */
 	tt=dead_cell->fwded_totags;
 	tt=dead_cell->fwded_totags;
 	while(tt) {
 	while(tt) {

+ 23 - 2
modules/tm/h_table.h

@@ -143,6 +143,10 @@ enum kill_reason { REQ_FWDED=1, REQ_RPLD=2, REQ_RLSD=4, REQ_EXIST=8,
 #define F_RB_REPLIED	0x20 /* reply received */
 #define F_RB_REPLIED	0x20 /* reply received */
 #define F_RB_CANCELED	0x40 /* rb/branch canceled */
 #define F_RB_CANCELED	0x40 /* rb/branch canceled */
 #define F_RB_DEL_TIMER	0x80 /* timer should be deleted if active */
 #define F_RB_DEL_TIMER	0x80 /* timer should be deleted if active */
+#define F_RB_NH_LOOSE	0x100 /* next hop is a loose router */
+#define F_RB_NH_STRICT	0x200 /* next hop is a strict router */
+/* must detect when neither loose nor strict flag is set -> two flags.
+ * alternatively, 1x flag for strict/loose and 1x for loose|strict set/not */
 
 
 
 
 /* if canceled or intended to be canceled, return true */
 /* if canceled or intended to be canceled, return true */
@@ -154,10 +158,10 @@ typedef struct retr_buf
 	short activ_type;
 	short activ_type;
 	/* set to status code if the buffer is a reply,
 	/* set to status code if the buffer is a reply,
 	0 if request or -1 if local CANCEL */
 	0 if request or -1 if local CANCEL */
-	volatile unsigned char flags; /* DISABLED, T2 */
+	volatile unsigned short flags; /* DISABLED, T2 */
 	volatile unsigned char t_active; /* timer active */
 	volatile unsigned char t_active; /* timer active */
 	unsigned short branch; /* no more then 65k branches :-) */
 	unsigned short branch; /* no more then 65k branches :-) */
-	short   buffer_len;
+	short buffer_len;
 	char *buffer;
 	char *buffer;
 	/*the cell that contains this retrans_buff*/
 	/*the cell that contains this retrans_buff*/
 	struct cell* my_T;
 	struct cell* my_T;
@@ -207,6 +211,16 @@ typedef struct ua_client
 	str              uri;
 	str              uri;
 	/* if we don't store, we at least want to know the status */
 	/* if we don't store, we at least want to know the status */
 	int             last_received;
 	int             last_received;
+#ifdef WITH_AS_SUPPORT
+	/**
+	 * Resent for every rcvd 2xx reply.
+	 * This member's as an alternative to passing the reply to the AS, 
+	 * every time a reply for local request is rcvd.
+	 * Member can not be union'ed with local_cancel, since CANCEL can happen
+	 * concurrently with a 2xx reply (to generate an ACK).
+	 */
+	struct retr_buf *local_ack;
+#endif
 }ua_client_type;
 }ua_client_type;
 
 
 
 
@@ -237,6 +251,10 @@ struct totag_elem {
 #define T_IN_AGONY (1<<5) /* set if waiting to die (delete timer)
 #define T_IN_AGONY (1<<5) /* set if waiting to die (delete timer)
                              TODO: replace it with del on unref */
                              TODO: replace it with del on unref */
 #define T_AUTO_INV_100 (1<<6) /* send an 100 reply automatically  to inv. */
 #define T_AUTO_INV_100 (1<<6) /* send an 100 reply automatically  to inv. */
+#ifdef WITH_AS_SUPPORT
+	/* don't generate automatically an ACK for local transaction */
+#	define T_NO_AUTO_ACK	(1<<7)
+#endif
 #define T_DONT_FORK   (T_CANCELED|T_6xx)
 #define T_DONT_FORK   (T_CANCELED|T_6xx)
 
 
 /* unsigned short should be enough for a retr. timer: max. 65535 ticks =>
 /* unsigned short should be enough for a retr. timer: max. 65535 ticks =>
@@ -441,6 +459,9 @@ inline static void insert_into_hash_table_unsafe( struct cell * p_cell,
 													unsigned int hash )
 													unsigned int hash )
 {
 {
 	p_cell->label = _tm_table->entries[hash].next_label++;
 	p_cell->label = _tm_table->entries[hash].next_label++;
+#ifdef EXTRA_DEBUG
+	DEBUG("cell label: %u\n", p_cell->label);
+#endif
 	p_cell->hash_index=hash;
 	p_cell->hash_index=hash;
 	/* insert at the beginning */
 	/* insert at the beginning */
 	clist_insert(&_tm_table->entries[hash], p_cell, next_c, prev_c);
 	clist_insert(&_tm_table->entries[hash], p_cell, next_c, prev_c);

+ 18 - 4
modules/tm/t_hooks.h

@@ -76,12 +76,15 @@ struct cell;
 #define TMCB_DESTROY_N          15  /* called on transaction destroy */
 #define TMCB_DESTROY_N          15  /* called on transaction destroy */
 #define TMCB_E2ECANCEL_IN_N     16
 #define TMCB_E2ECANCEL_IN_N     16
 #define TMCB_E2EACK_RETR_IN_N   17
 #define TMCB_E2EACK_RETR_IN_N   17
+#ifdef WITH_AS_SUPPORT
+#define TMCB_DONT_ACK_N         18 /* TM shoudn't ACK a local UAC  */
+#endif
 #ifdef TMCB_ONSEND
 #ifdef TMCB_ONSEND
-#define TMCB_REQUEST_SENT_N     18
-#define TMCB_RESPONSE_SENT_N    19
-#define TMCB_MAX_N              19
+#define TMCB_REQUEST_SENT_N     19
+#define TMCB_RESPONSE_SENT_N    20
+#define TMCB_MAX_N              20
 #else
 #else
-#define TMCB_MAX_N              17
+#define TMCB_MAX_N              18
 #endif
 #endif
 
 
 
 
@@ -103,6 +106,9 @@ struct cell;
 #define TMCB_DESTROY          (1<<TMCB_DESTROY_N)
 #define TMCB_DESTROY          (1<<TMCB_DESTROY_N)
 #define TMCB_E2ECANCEL_IN     (1<<TMCB_E2ECANCEL_IN_N)
 #define TMCB_E2ECANCEL_IN     (1<<TMCB_E2ECANCEL_IN_N)
 #define TMCB_E2EACK_RETR_IN   (1<<TMCB_E2EACK_RETR_IN_N)
 #define TMCB_E2EACK_RETR_IN   (1<<TMCB_E2EACK_RETR_IN_N)
+#ifdef WITH_AS_SUPPORT
+#define TMCB_DONT_ACK         (1<<TMCB_DONT_ACK_N)
+#endif
 #ifdef TMCB_ONSEND
 #ifdef TMCB_ONSEND
 #define TMCB_REQUEST_SENT      (1<<TMCB_REQUEST_SENT_N)
 #define TMCB_REQUEST_SENT      (1<<TMCB_REQUEST_SENT_N)
 #define TMCB_RESPONSE_SENT     (1<<TMCB_RESPONSE_SENT_N)
 #define TMCB_RESPONSE_SENT     (1<<TMCB_RESPONSE_SENT_N)
@@ -313,6 +319,14 @@ struct cell;
  *  the cell* parameter (t) and the tmcb are set to 0. Only the param is
  *  the cell* parameter (t) and the tmcb are set to 0. Only the param is
  *  is filled inside TMCB. For dialogs callbacks t is also 0.
  *  is filled inside TMCB. For dialogs callbacks t is also 0.
  *
  *
+ *
+ * TMCB_DONT_ACK (requires AS support) -- for localy generated INVITEs, TM 
+ * automatically generates an ACK for the received 2xx replies. But, if this 
+ * flag is passed to TM when creating the initial UAC request, this won't
+ * happen anymore: the ACK generation must be triggered from outside, using
+ * TM's interface.
+ * While this isn't exactly a callback type, it is used as part of the flags
+ * mask when registering callbacks.
 
 
 	the callback's param MUST be in shared memory and will
 	the callback's param MUST be in shared memory and will
 	NOT be freed by TM; you must do it yourself from the
 	NOT be freed by TM; you must do it yourself from the

+ 30 - 1
modules/tm/t_lookup.c

@@ -1500,6 +1500,31 @@ int t_get_trans_ident(struct sip_msg* p_msg, unsigned int* hash_index, unsigned
     return 1;
     return 1;
 }
 }
 
 
+#ifdef WITH_AS_SUPPORT
+/**
+ * Returns the hash coordinates of the transaction current CANCEL is targeting.
+ */
+int t_get_canceled_ident(struct sip_msg* msg, unsigned int* hash_index, 
+		unsigned int* label)
+{
+	struct cell *orig;
+	if (msg->REQ_METHOD != METHOD_CANCEL) {
+		WARN("looking up original transaction for non-CANCEL method (%d).\n",
+				msg->REQ_METHOD);
+		return -1;
+	}
+	orig = t_lookupOriginalT(msg);
+	if ((orig == T_NULL_CELL) || (orig == T_UNDEFINED))
+		return -1;
+	*hash_index = orig->hash_index;
+	*label = orig->label;
+	DEBUG("original T found @%p, %d:%d.\n", orig, *hash_index, *label);
+	/* TODO: why's w_t_lookup_cancel setting T to 'undefined'?? */
+	UNREF(orig);
+	return 1;
+}
+#endif /* WITH_AS_SUPPORT */
+
 int t_lookup_ident(struct cell ** trans, unsigned int hash_index, 
 int t_lookup_ident(struct cell ** trans, unsigned int hash_index, 
 					unsigned int label)
 					unsigned int label)
 {
 {
@@ -1512,7 +1537,11 @@ int t_lookup_ident(struct cell ** trans, unsigned int hash_index,
 	}
 	}
 	
 	
 	LOCK_HASH(hash_index);
 	LOCK_HASH(hash_index);
-	
+
+#ifndef E2E_CANCEL_HOP_BY_HOP
+#warning "t_lookup_ident() can only reliably match INVITE transactions in " \
+		"E2E_CANCEL_HOP_BY_HOP mode"
+#endif
 	hash_bucket=&(get_tm_table()->entries[hash_index]);
 	hash_bucket=&(get_tm_table()->entries[hash_index]);
 	/* all the transactions from the entry are compared */
 	/* all the transactions from the entry are compared */
 	clist_foreach(hash_bucket, p_cell, next_c){
 	clist_foreach(hash_bucket, p_cell, next_c){

+ 504 - 77
modules/tm/t_msgbuilder.c

@@ -50,7 +50,9 @@
 
 
 #include "defs.h"
 #include "defs.h"
 
 
-
+#ifdef EXTRA_DEBUG
+#include <assert.h>
+#endif
 #include "../../comp_defs.h"
 #include "../../comp_defs.h"
 #include "../../hash_func.h"
 #include "../../hash_func.h"
 #include "../../globals.h"
 #include "../../globals.h"
@@ -375,10 +377,13 @@ error:
 }
 }
 
 
 
 
-struct rte {
+typedef struct rte {
 	rr_t* ptr;
 	rr_t* ptr;
+	/* 'ptr' above doesn't point to a mem chunk linked to a sip_msg, so it
+	 * won't be free'd along with it => it must be free'd "manually" */
+	int free_rr;
 	struct rte* next;
 	struct rte* next;
-};
+} rte_t;
 
 
   	 
   	 
 static inline void free_rte_list(struct rte* list)
 static inline void free_rte_list(struct rte* list)
@@ -388,74 +393,13 @@ static inline void free_rte_list(struct rte* list)
 	while(list) {
 	while(list) {
 		ptr = list;
 		ptr = list;
 		list = list->next;
 		list = list->next;
+		if (ptr->free_rr)
+			free_rr(&ptr->ptr);
 		pkg_free(ptr);
 		pkg_free(ptr);
 	}
 	}
 }
 }
 
 
 
 
-static inline int process_routeset(struct sip_msg* msg, str* contact, struct rte** list, str* ruri, str* next_hop)
-{
-	struct hdr_field* ptr;
-	rr_t* p;
-	struct rte* t, *head;
-	struct sip_uri puri;
-	
-	ptr = msg->record_route;
-	head = 0;
-	while(ptr) {
-		if (ptr->type == HDR_RECORDROUTE_T) {
-			if (parse_rr(ptr) < 0) {
-				LOG(L_ERR, "process_routeset: Error while parsing Record-Route header\n");
-				return -1;
-			}
-			
-			p = (rr_t*)ptr->parsed;
-			while(p) {
-				t = (struct rte*)pkg_malloc(sizeof(struct rte));
-				if (!t) {
-					LOG(L_ERR, "process_routeset: No memory left\n");
-					free_rte_list(head);
-					return -1;
-				}
-				t->ptr = p;
-				t->next = head;
-				head = t;
-				p = p->next;
-			}
-		}
-		ptr = ptr->next;
-	}
-	
-	if (head) {
-		if (parse_uri(head->ptr->nameaddr.uri.s, head->ptr->nameaddr.uri.len, &puri) == -1) {
-			LOG(L_ERR, "process_routeset: Error while parsing URI\n");
-			free_rte_list(head);
-			return -1;
-		}
-		
-		if (puri.lr.s) {
-			     /* Next hop is loose router */
-			*ruri = *contact;
-			*next_hop = head->ptr->nameaddr.uri;
-		} else {
-			     /* Next hop is strict router */
-			*ruri = head->ptr->nameaddr.uri;
-			*next_hop = *ruri;
-			t = head;
-			head = head->next;
-			pkg_free(t);
-		}
-	} else {
-		     /* No routes */
-		*ruri = *contact;
-		*next_hop = *contact;
-	}
-	
-	*list = head;
-	return 0;
-}
-
-
 static inline int calc_routeset_len(struct rte* list, str* contact)
 static inline int calc_routeset_len(struct rte* list, str* contact)
 {
 {
 	struct rte* ptr;
 	struct rte* ptr;
@@ -547,7 +491,429 @@ static inline int get_contact_uri(struct sip_msg* msg, str* uri)
 	return 0;
 	return 0;
 }
 }
 
 
+/**
+ * Extract route set from the message (out of Record-Route, if reply, OR
+ * Route, if request).
+ * The route set is returned into the "UAC-format" (keep order for Rs, reverse
+ * RRs).
+ */
+static inline int get_uac_rs(sip_msg_t *msg, int is_req, struct rte **rtset)
+{
+	struct hdr_field* ptr;
+	rr_t *p, *new_p;
+	struct rte *t, *head, *old_head;
+
+	head = 0;
+	for (ptr = is_req ? msg->route : msg->record_route; ptr; ptr = ptr->next) {
+		switch (ptr->type) {
+			case HDR_RECORDROUTE_T:
+				if (is_req)
+					continue;
+				break;
+			case HDR_ROUTE_T:
+				if (! is_req)
+					continue;
+				break;
+			default:
+				continue;
+		}
+		if (parse_rr(ptr) < 0) {
+			ERR("failed to parse Record-/Route HF (%d).\n", ptr->type);
+			goto err;
+		}
+			
+		p = (rr_t*)ptr->parsed;
+		while(p) {
+			if (! (t = (struct rte*)pkg_malloc(sizeof(struct rte)))) {
+				ERR("out of pkg mem (asked for: %zd).\n", sizeof(struct rte));
+				goto err;
+			}
+			if (is_req) {
+				/* in case of requests, the sip_msg structure is free'd before
+				 * rte list is evaluated => must do a copy of it */
+				if (duplicate_rr(&new_p, p) < 0) {
+					pkg_free(t);
+					ERR("failed to duplicate RR");
+					goto err;
+				}
+				t->ptr = new_p;
+			} else {
+				t->ptr = p;
+			}
+			t->free_rr = is_req;
+			t->next = head;
+			head = t;
+			p = p->next;
+		}
+	}
 
 
+	if (is_req) {
+		/* harvesting the R/RR HF above inserts at head, which suites RRs (as
+		 * they must be reversed, anyway), but not Rs => reverse once more */
+		old_head = head;
+		head = 0;
+		while (old_head) {
+			t = old_head;
+			old_head = old_head->next;
+			t->next = head;
+			head = t;
+		}
+	}
+
+	*rtset = head;
+	return 0;
+err:
+	free_rte_list(head);
+	return -1;
+}
+
+
+static inline unsigned short uri2port(const struct sip_uri *puri)
+{
+	if (puri->port.s) {
+		return puri->port_no;
+	} else switch (puri->type) {
+		case SIP_URI_T:
+		case TEL_URI_T:
+			if (puri->transport_val.len == sizeof("TLS") - 1) {
+				unsigned trans;
+				trans = puri->transport_val.s[0] | 0x20; trans <<= 8;
+				trans |= puri->transport_val.s[1] | 0x20; trans <<= 8;
+				trans |= puri->transport_val.s[2] | 0x20;
+				if (trans == 0x746C73) /* t l s */
+					return SIPS_PORT;
+			}
+			return SIP_PORT;
+		case SIPS_URI_T:
+		case TELS_URI_T:
+			return SIPS_PORT;
+		default:
+			BUG("unexpected URI type %d.\n", puri->type);
+	}
+	return 0;
+}
+
+/**
+ * Evaluate if next hop is a strict or loose router, by looking at the
+ * retr. buffer of the original INVITE.
+ * Assumes:
+ * 	orig_inv is a parsed SIP message;
+ * 	rtset is not NULL.
+ * @return:
+ * 	F_RB_NH_LOOSE : next hop was loose router;
+ * 	F_RB_NH_STRICT: nh is strict;
+ * 	0 on error.
+ */
+static unsigned long nhop_type(sip_msg_t *orig_inv, rte_t *rtset,
+		const struct dest_info *dst_inv, str *contact)
+{
+	struct sip_uri puri, topr_uri, lastr_uri, inv_ruri, cont_uri;
+	struct ip_addr *uri_ia;
+	union sockaddr_union uri_sau;
+	unsigned int uri_port, dst_port, inv_port, cont_port, lastr_port;
+	rte_t *last_r;
+#ifdef TM_LOC_ACK_DO_REV_DNS
+	struct ip_addr ia;
+	struct hostent *he;
+	char **alias;
+#endif
+
+#define PARSE_URI(_str_, _uri_) \
+	do { \
+		/* parse_uri() 0z the puri */ \
+		if (parse_uri((_str_)->s, \
+				(_str_)->len, _uri_) < 0) { \
+			ERR("failed to parse route body '%.*s'.\n", STR_FMT(_str_)); \
+			return 0; \
+		} \
+	} while (0)
+
+#define HAS_LR(_rte_) \
+	({ \
+		PARSE_URI(&(_rte_)->ptr->nameaddr.uri, &puri); \
+		puri.lr.s; \
+	})
+
+#define URI_PORT(_puri_, _port) \
+	do { \
+		if (! (_port = uri2port(_puri_))) \
+			return 0; \
+	} while (0)
+
+	/* examine the easy/fast & positive cases foremost */
+
+	/* [1] check if 1st route lacks ;lr */
+	DEBUG("checking lack of ';lr' in 1st route.\n");
+	if (! HAS_LR(rtset))
+		return F_RB_NH_STRICT;
+	topr_uri = puri; /* save 1st route's URI */
+
+	/* [2] check if last route shows ;lr */
+	DEBUG("checking presence of ';lr' in last route.\n");
+	for (last_r = rtset; last_r->next; last_r = last_r->next)
+		/* scroll down to last route */
+		;
+	if (HAS_LR(last_r))
+		return F_RB_NH_LOOSE;
+
+	/* [3] 1st route has ;lr -> check if the destination of original INV
+	 * equals the address provided by this route; if does -> loose */
+	DEBUG("checking INVITE's destination against its first route.\n");
+	URI_PORT(&topr_uri, uri_port);
+	if (! (dst_port = su_getport((void *)&dst_inv->to)))
+		return 0; /* not really expected */
+	if (dst_port != uri_port)
+		return F_RB_NH_STRICT;
+	/* if 1st route contains an IP address, comparing it against .dst */
+	if ((uri_ia = str2ip(&topr_uri.host))
+#ifdef USE_IPV6
+			|| (uri_ia = str2ip6(&topr_uri.host))
+#endif
+			) {
+		/* we have an IP address in route -> comparison can go swiftly */
+		if (init_su(&uri_sau, uri_ia, uri_port) < 0)
+			return 0; /* not really expected */
+		if (su_cmp(&uri_sau, (void *)&dst_inv->to))
+			/* ;lr and sent there */
+			return F_RB_NH_LOOSE;
+		else
+			/* ;lr and NOT sent there (probably sent to RURI address) */
+			return F_RB_NH_STRICT;
+	} else {
+		/*if 1st route contains a name, rev resolve the .dst and compare*/
+		INFO("Failed to decode string '%.*s' in route set element as IP "
+				"address. Trying name resolution.\n",STR_FMT(&topr_uri.host));
+
+	/* TODO: alternatively, rev name and compare against dest. IP.  */
+#ifdef TM_LOC_ACK_DO_REV_DNS
+		ia.af = 0;
+		su2ip_addr(&ia, (void *)&dst_inv->to);
+		if (! ia.af)
+			return 0; /* not really expected */
+		if ((he = rev_resolvehost(&ia))) {
+			if ((strlen(he->h_name) == topr_uri.host.len) &&
+					(memcmp(he->h_name, topr_uri.host.s, 
+							topr_uri.host.len) == 0))
+				return F_RB_NH_LOOSE;
+			for (alias = he->h_aliases; *alias; alias ++)
+				if ((strlen(*alias) == topr_uri.host.len) &&
+						(memcmp(*alias, topr_uri.host.s, 
+								topr_uri.host.len) == 0))
+					return F_RB_NH_LOOSE;
+			return F_RB_NH_STRICT;
+		} else {
+			INFO("failed to resolve address '%s' to a name.\n", 
+					ip_addr2a(&ia));
+		}
+#endif
+	}
+
+	WARN("failed to establish with certainty the type of next hop; trying an"
+			" educated guess.\n");
+
+	/* [4] compare (possibly updated) remote target to original RURI; if
+	 * equal, a strict router's address wasn't filled in as RURI -> loose */
+	DEBUG("checking remote target against INVITE's RURI.\n");
+	PARSE_URI(contact, &cont_uri);
+	PARSE_URI(GET_RURI(orig_inv), &inv_ruri);
+	URI_PORT(&cont_uri, cont_port);
+	URI_PORT(&inv_ruri, inv_port);
+	if ((cont_port == inv_port) && (cont_uri.host.len == inv_ruri.host.len) &&
+			(memcmp(cont_uri.host.s, inv_ruri.host.s, cont_uri.host.len) == 0))
+		return F_RB_NH_LOOSE;
+
+	/* [5] compare (possibly updated) remote target to last route; if equal, 
+	 * strict router's address might have been filled as RURI and remote
+	 * target appended to route set -> strict */
+	DEBUG("checking remote target against INVITE's last route.\n");
+	PARSE_URI(&last_r->ptr->nameaddr.uri, &lastr_uri);
+	URI_PORT(&lastr_uri, lastr_port);
+	if ((cont_port == lastr_port) && 
+			(cont_uri.host.len == lastr_uri.host.len) &&
+			(memcmp(cont_uri.host.s, lastr_uri.host.s, 
+					lastr_uri.host.len) == 0))
+		return F_RB_NH_STRICT;
+
+	WARN("failed to establish the type of next hop; assuming loose router.\n");
+	return F_RB_NH_LOOSE;
+
+#undef PARSE_URI
+#undef HAS_LR
+#undef URI_PORT
+}
+
+/**
+ * Evaluates the routing elements in locally originated request or reply to
+ * locally originated request.
+ * If original INVITE was in-dialog (had to-tag), it uses the
+ * routes present there (b/c the 2xx for it does not have a RR set, normally).
+ * Otherwise, use the reply (b/c the INVITE does not have yet the complete 
+ * route set).
+ *
+ * @return: negative for failure; out params:
+ *  - list: route set;
+ *  - ruri: RURI to be used in ACK;
+ *  - nexthop: where to first send the ACK.
+ *
+ *  NOTE: assumes rpl's parsed to EOF!
+ *
+ */
+static int eval_uac_routing(sip_msg_t *rpl, const struct retr_buf *inv_rb, 
+		str* contact, struct rte **list, str *ruri, str *next_hop)
+{
+	sip_msg_t orig_inv, *sipmsg; /* reparse original INVITE */
+	rte_t *t, *prev_t, *rtset = NULL;
+	int is_req;
+	struct sip_uri puri;
+	static size_t chklen;
+	int ret = -1;
+	
+	/* parse the retr. buffer */
+	memset(&orig_inv, 0, sizeof(struct sip_msg));
+	orig_inv.buf = inv_rb->buffer;
+	orig_inv.len = inv_rb->buffer_len;
+	DEBUG("reparsing retransmission buffer of original INVITE:\n%.*s\n",
+			orig_inv.len, orig_inv.buf);
+	if (parse_msg(orig_inv.buf, orig_inv.len, &orig_inv) != 0) {
+		ERR("failed to parse retr buffer (weird!): \n%.*s\n", orig_inv.len,
+				orig_inv.buf);
+		return -1;
+	}
+
+	/* check if we need to look at request or reply */
+	if ((parse_headers(&orig_inv, HDR_TO_F, 0) < 0) || (! orig_inv.to)) {
+		/* the bug is at message assembly */
+		BUG("failed to parse INVITE retr. buffer and/or extract 'To' HF:"
+				"\n%.*s\n", orig_inv.len, orig_inv.buf);
+		goto end;
+	}
+	if (((struct to_body *)orig_inv.to->parsed)->tag_value.len) {
+		DEBUG("building ACK for in-dialog INVITE (using RS in orig. INV.)\n");
+		if (parse_headers(&orig_inv, HDR_EOH_F, 0) < 0) {
+			BUG("failed to parse INVITE retr. buffer to EOH:"
+					"\n%.*s\n", orig_inv.len, orig_inv.buf);
+			goto end;
+		}
+		sipmsg = &orig_inv;
+		is_req = 1;
+	} else {
+		DEBUG("building ACK for out-of-dialog INVITE (using RS in RR set).\n");
+		sipmsg = rpl;
+		is_req = 0;
+	}
+
+	/* extract the route set */
+	if (get_uac_rs(sipmsg, is_req, &rtset) < 0) {
+		ERR("failed to extract route set.\n");
+		goto end;
+	}
+
+	if (! rtset) { /* No routes */
+		*ruri = *contact;
+		*next_hop = *contact;
+	} else if (! is_req) { /* out of dialog req. */
+		if (parse_uri(rtset->ptr->nameaddr.uri.s, rtset->ptr->nameaddr.uri.len,
+				&puri) < 0) {
+			ERR("failed to parse first route in set.\n");
+			goto end;
+		}
+		
+		if (puri.lr.s) { /* Next hop is loose router */
+			*ruri = *contact;
+			*next_hop = rtset->ptr->nameaddr.uri;
+		} else { /* Next hop is strict router */
+			*ruri = rtset->ptr->nameaddr.uri;
+			*next_hop = *ruri;
+			/* consume first route, b/c it will be put in RURI */
+			t = rtset;
+			rtset = rtset->next;
+			pkg_free(t);
+		}
+	} else {
+		unsigned long route_flags = inv_rb->flags;
+		DEBUG("UAC rb flags: 0x%x.\n", (unsigned int)route_flags);
+eval_flags:
+		switch (route_flags & (F_RB_NH_LOOSE|F_RB_NH_STRICT)) {
+		case 0:
+			WARN("calculate_hooks() not called when built the local UAC of "
+					"in-dialog request, or called with empty route set.\n");
+			/* try to figure out what kind of hop is the next one
+			 * (strict/loose) by reading the original invite */
+			if ((route_flags = nhop_type(&orig_inv, rtset, &inv_rb->dst, 
+					contact))) {
+				DEBUG("original request's next hop type evaluated to: 0x%x.\n",
+						(unsigned int)route_flags);
+				goto eval_flags;
+			} else {
+				ERR("failed to establish what kind of router the next "
+						"hop is.\n");
+				goto end;
+			}
+			break;
+		case F_RB_NH_LOOSE:
+			*ruri = *contact;
+			*next_hop = rtset->ptr->nameaddr.uri;
+			break;
+		case F_RB_NH_STRICT:
+			/* find ptr to last route body that contains the (possibly) old 
+			 * remote target 
+			 */
+			for (t = rtset, prev_t = t; t->next; prev_t = t, t = t->next)
+				;
+			if ((t->ptr->len == contact->len) && 
+					(memcmp(t->ptr->nameaddr.name.s, contact->s, 
+							contact->len) == 0)){
+				/* the remote target didn't update -> keep the whole route set,
+				 * including the last entry */
+				/* do nothing */
+			} else {
+				/* trash last entry and replace with new remote target */
+				free_rte_list(t);
+				/* compact the rr_t struct along with rte. this way, free'ing
+				 * it can be done along with rte chunk, independent of Route
+				 * header parser's allocator (using pkg/shm) */
+				chklen = sizeof(struct rte) + sizeof(rr_t);
+				if (! (t = (struct rte *)pkg_malloc(chklen))) {
+					ERR("out of pkg memory (%zd required)\n", chklen);
+					goto end;
+				}
+				/* this way, .free_rr is also set to 0 (!!!) */
+				memset(t, 0, chklen); 
+				((rr_t *)&t[1])->nameaddr.name = *contact;
+				((rr_t *)&t[1])->len = contact->len;
+				/* chain the new route elem in set */
+				if (prev_t == rtset)
+				 	/*there is only one elem in route set: the remote target*/
+					rtset = t;
+				else
+					prev_t->next = t;
+			}
+
+			*ruri = *GET_RURI(&orig_inv); /* reuse original RURI */
+			*next_hop = *ruri;
+			break;
+		default:
+			/* probably a mem corruption */
+			BUG("next hop of original request marked as both loose and strict"
+					" router (buffer: %.*s).\n", inv_rb->buffer_len, 
+					inv_rb->buffer);
+#ifdef EXTRA_DEBUG
+			abort();
+#else
+			goto end;
+#endif
+		}
+	}
+
+	*list = rtset;
+	/* all went well */
+	ret = 0;
+end:
+	free_sip_msg(&orig_inv);
+	if (ret < 0)
+		free_rte_list(rtset);
+	return ret;
+}
 
 
      /*
      /*
       * The function creates an ACK to 200 OK. Route set will be created
       * The function creates an ACK to 200 OK. Route set will be created
@@ -556,8 +922,8 @@ static inline int get_contact_uri(struct sip_msg* msg, str* uri)
 	  * generates local ACK to 200 OK (on behalf of applications using uac)
 	  * generates local ACK to 200 OK (on behalf of applications using uac)
       */
       */
 char *build_dlg_ack(struct sip_msg* rpl, struct cell *Trans, 
 char *build_dlg_ack(struct sip_msg* rpl, struct cell *Trans, 
-					unsigned int branch, str* to, unsigned int *len,
-					struct dest_info* dst)
+					unsigned int branch, str *hdrs, str *body,
+					unsigned int *len, struct dest_info* dst)
 {
 {
 	char *req_buf, *p, *via;
 	char *req_buf, *p, *via;
 	unsigned int via_len;
 	unsigned int via_len;
@@ -568,18 +934,47 @@ char *build_dlg_ack(struct sip_msg* rpl, struct cell *Trans,
 	struct rte* list;
 	struct rte* list;
 	str contact, ruri, *cont;
 	str contact, ruri, *cont;
 	str next_hop;
 	str next_hop;
+	str body_len;
+	str _to, *to = &_to;
 #ifdef USE_DNS_FAILOVER
 #ifdef USE_DNS_FAILOVER
 	struct dns_srv_handle dns_h;
 	struct dns_srv_handle dns_h;
 #endif
 #endif
+#ifdef WITH_AS_SUPPORT
+	/* With AS support, TM allows for external modules to generate building of
+	 * the ACK; in this case, the ACK's retransmission buffer is built once
+	 * and kept in memory (to help when retransmitted 2xx are received and ACK
+	 * must be resent).
+	 * Allocation of the string raw buffer that holds the ACK is piggy-backed
+	 * with allocation of the retransmission buffer (since both have the same
+	 * life-cycle): both the string buffer and retransm. buffer are placed 
+	 * into the same allocated chunk of memory (retr. buffer first, string 
+	 * buffer follows).In this case, the 'len' param is used as in-out 
+	 * parameter: 'in' to give the extra space needed by the retr. buffer,
+	 * 'out' to return the lenght of the allocated string buffer.
+	 */
+	unsigned offset = *len;
+#endif
 	
 	
-	if (get_contact_uri(rpl, &contact) < 0) {
+	if (parse_headers(rpl, HDR_EOH_F, 0) == -1 || !rpl->to) {
+		ERR("Error while parsing headers.\n");
 		return 0;
 		return 0;
+	} else {
+		_to.s = rpl->to->name.s;
+		_to.len = rpl->to->len;
 	}
 	}
 	
 	
-	if (process_routeset(rpl, &contact, &list, &ruri, &next_hop) < 0) {
+	if (get_contact_uri(rpl, &contact) < 0) {
 		return 0;
 		return 0;
 	}
 	}
 	
 	
+	if (eval_uac_routing(rpl, &Trans->uac[branch].request, &contact, 
+			&list, &ruri, &next_hop) < 0) {
+		ERR("failed to evaluate routing elements.\n");
+		return 0;
+	}
+	DEBUG("ACK RURI: `%.*s', NH: `%.*s'.\n", STR_FMT(&ruri), 
+			STR_FMT(&next_hop));
+
 	if ((contact.s != ruri.s) || (contact.len != ruri.len)) {
 	if ((contact.s != ruri.s) || (contact.len != ruri.len)) {
 		     /* contact != ruri means that the next
 		     /* contact != ruri means that the next
 		      * hop is a strict router, cont will be non-zero
 		      * hop is a strict router, cont will be non-zero
@@ -643,12 +1038,29 @@ char *build_dlg_ack(struct sip_msg* rpl, struct cell *Trans,
 	
 	
 	     /* User Agent */
 	     /* User Agent */
 	if (server_signature) *len += USER_AGENT_LEN + CRLF_LEN;
 	if (server_signature) *len += USER_AGENT_LEN + CRLF_LEN;
+		/* extra headers */
+	if (hdrs)
+		*len += hdrs->len;
+		/* body */
+	if (body) {
+		body_len.s = int2str(body->len, &body_len.len);
+		*len += body->len;
+	} else {
+		body_len.len = 0;
+		body_len.s = NULL; /*4gcc*/
+		*len += 1; /* for the (Cont-Len:) `0' */
+	}
 	     /* Content Length, EoM */
 	     /* Content Length, EoM */
-	*len += CONTENT_LENGTH_LEN + 1 + CRLF_LEN + CRLF_LEN;
-	
+	*len += CONTENT_LENGTH_LEN + body_len.len + CRLF_LEN + CRLF_LEN;
+
+#if WITH_AS_SUPPORT
+	req_buf = shm_malloc(offset + *len + 1);
+	req_buf += offset;
+#else
 	req_buf = shm_malloc(*len + 1);
 	req_buf = shm_malloc(*len + 1);
+#endif
 	if (!req_buf) {
 	if (!req_buf) {
-		LOG(L_ERR, "build_dlg_ack: Cannot allocate memory\n");
+		ERR("Cannot allocate memory (%u+1)\n", *len);
 		goto error01;
 		goto error01;
 	}
 	}
 	p = req_buf;
 	p = req_buf;
@@ -679,8 +1091,23 @@ char *build_dlg_ack(struct sip_msg* rpl, struct cell *Trans,
 		append_mem_block(p, USER_AGENT CRLF, USER_AGENT_LEN + CRLF_LEN);
 		append_mem_block(p, USER_AGENT CRLF, USER_AGENT_LEN + CRLF_LEN);
 	}
 	}
 	
 	
-	     /* Content Length, EoM */
-	append_mem_block(p, CONTENT_LENGTH "0" CRLF CRLF, CONTENT_LENGTH_LEN + 1 + CRLF_LEN + CRLF_LEN);
+	/* extra headers */
+	if (hdrs)
+		append_mem_block(p, hdrs->s, hdrs->len);
+	
+	     /* Content Length, EoH, (body) */
+	if (body) {
+		append_mem_block(p, CONTENT_LENGTH, CONTENT_LENGTH_LEN);
+		append_mem_block(p, body_len.s, body_len.len);
+		append_mem_block(p, /*end crr. header*/CRLF /*EoH*/CRLF, CRLF_LEN + 
+				CRLF_LEN);
+		append_mem_block(p, body->s, body->len);
+	} else {
+		append_mem_block(p, CONTENT_LENGTH "0" CRLF CRLF, 
+				CONTENT_LENGTH_LEN + 1 + CRLF_LEN + CRLF_LEN);
+	}
+
+	/* EoM */
 	*p = 0;
 	*p = 0;
 	
 	
 	pkg_free(via);
 	pkg_free(via);
@@ -692,7 +1119,7 @@ char *build_dlg_ack(struct sip_msg* rpl, struct cell *Trans,
  error:
  error:
 	free_rte_list(list);
 	free_rte_list(list);
 	return 0;
 	return 0;
-  	 }
+}
 
 
 
 
 /*
 /*
@@ -959,7 +1386,7 @@ char* build_uac_req(str* method, str* headers, str* body, dlg_t* dialog, int bra
      	if (body) memapp(w, body->s, body->len);
      	if (body) memapp(w, body->s, body->len);
 
 
 #ifdef EXTRA_DEBUG
 #ifdef EXTRA_DEBUG
-	if (w-buf != *len ) abort();
+	assert(w-buf == *len);
 #endif
 #endif
 
 
 	pkg_free(via.s);
 	pkg_free(via.s);

+ 2 - 2
modules/tm/t_msgbuilder.h

@@ -72,8 +72,8 @@ char *build_uac_request(  str msg_type, str dst, str from,
  * local ACK to 200 OK (on behalf of applications using uac
  * local ACK to 200 OK (on behalf of applications using uac
  */
  */
 char *build_dlg_ack(struct sip_msg* rpl, struct cell *Trans, 
 char *build_dlg_ack(struct sip_msg* rpl, struct cell *Trans, 
-					unsigned int branch, str* to, unsigned int *len,
-					struct dest_info *dst);
+					unsigned int branch, str *hdrs, str *body,
+					unsigned int *len, struct dest_info* dst);
 
 
 
 
 /*
 /*

+ 48 - 9
modules/tm/t_reply.c

@@ -93,7 +93,9 @@
  */
  */
 
 
 
 
-
+#ifdef EXTRA_DEBUG
+#include <assert.h>
+#endif
 #include "../../comp_defs.h"
 #include "../../comp_defs.h"
 #include "../../hash_func.h"
 #include "../../hash_func.h"
 #include "../../dprint.h"
 #include "../../dprint.h"
@@ -368,15 +370,43 @@ static char *build_local_ack(struct sip_msg* rpl, struct cell *trans,
 								int branch, unsigned int *ret_len,
 								int branch, unsigned int *ret_len,
 								struct dest_info*  dst)
 								struct dest_info*  dst)
 {
 {
-	str to;
-	if (parse_headers(rpl, HDR_EOH_F, 0) == -1 || !rpl->to) {
-		LOG(L_ERR, "ERROR: build_local_ack: Error while parsing headers\n");
-		return 0;
+#ifdef WITH_AS_SUPPORT
+	struct retr_buf *local_ack, *old_lack;
+
+	/* do we have the ACK cache, previously build? */
+	if ((local_ack = trans->uac[0].local_ack) && local_ack->buffer_len) {
+		DEBUG("reusing ACK retr. buffer.\n");
+		*ret_len = local_ack->buffer_len;
+		*dst = local_ack->dst;
+		return local_ack->buffer;
+	}
+
+	/* the ACK will be built (and cached) by the AS (ack_local_uac()) */
+	if (trans->flags & T_NO_AUTO_ACK) 
+		return NULL;
+
+	if (! (local_ack = local_ack_rb(rpl, trans, branch, /*hdrs*/NULL, 
+			/*body*/NULL))) {
+		ERR("failed to build local ACK retransmission buffer (T@%p).\n",trans);
+		return NULL;
 	}
 	}
 
 
-	to.s = rpl->to->name.s;
-	to.len = rpl->to->len;
-	return build_dlg_ack(rpl, trans, branch, &to, ret_len, dst);
+	/* set the new buffer, but only if not already set (concurrent 2xx) */
+	if ((old_lack = (struct retr_buf *)atomic_cmpxchg_long(
+			(void *)&trans->uac[0].local_ack, 0, (long)local_ack))) {
+		/* buffer already set: trash current and use the winning one */
+		INFO("concurrent 2xx to local INVITE detected (T@%p).\n", trans);
+		free_local_ack(local_ack);
+		local_ack = old_lack;
+	}
+	
+	*ret_len = local_ack->buffer_len;
+	*dst = local_ack->dst;
+	return local_ack->buffer;
+#else /* ! WITH_AS_SUPPORT */
+	return build_dlg_ack(rpl, trans, branch, /*hdrs*/NULL, /*body*/NULL, 
+			ret_len, dst);
+#endif /* WITH_AS_SUPPORT */
 }
 }
 
 
 
 
@@ -1125,9 +1155,16 @@ static enum rps t_should_relay_response( struct cell *Trans , int new_code,
 
 
 	/* not >=300 ... it must be 2xx or provisional 1xx */
 	/* not >=300 ... it must be 2xx or provisional 1xx */
 	if (new_code>=100) {
 	if (new_code>=100) {
+#ifdef WITH_AS_SUPPORT
+			/* need a copy of the message for ACK generation */
+			*should_store = (inv_through && is_local(Trans) && 
+					(Trans->uac[branch].last_received < 200) &&
+					(Trans->flags & T_NO_AUTO_ACK)) ? 1 : 0;
+#else
+		*should_store=0;
+#endif
 		/* 1xx and 2xx except 100 will be relayed */
 		/* 1xx and 2xx except 100 will be relayed */
 		Trans->uac[branch].last_received=new_code;
 		Trans->uac[branch].last_received=new_code;
-		*should_store=0;
 		*should_relay= new_code==100? -1 : branch;
 		*should_relay= new_code==100? -1 : branch;
 		if (new_code>=200 ) {
 		if (new_code>=200 ) {
 			which_cancel( Trans, cancel_bitmap );
 			which_cancel( Trans, cancel_bitmap );
@@ -1818,7 +1855,9 @@ int reply_received( struct sip_msg  *p_msg )
 													&onsend_params);
 													&onsend_params);
 					}
 					}
 #endif
 #endif
+#ifndef WITH_AS_SUPPORT
 					shm_free(ack);
 					shm_free(ack);
+#endif
 				}
 				}
 			}
 			}
 		}
 		}

+ 143 - 14
modules/tm/uac.c

@@ -194,6 +194,7 @@ static inline int t_uac_prepare(uac_req_t *uac_r,
 #ifdef USE_DNS_FAILOVER
 #ifdef USE_DNS_FAILOVER
 	struct dns_srv_handle dns_h;
 	struct dns_srv_handle dns_h;
 #endif
 #endif
+	long nhtype;
 
 
 	ret=-1;
 	ret=-1;
 	hi=0; /* make gcc happy */
 	hi=0; /* make gcc happy */
@@ -203,7 +204,8 @@ static inline int t_uac_prepare(uac_req_t *uac_r,
 	/*** added by dcm 
 	/*** added by dcm 
 	 * - needed by external ua to send a request within a dlg
 	 * - needed by external ua to send a request within a dlg
 	 */
 	 */
-	if (w_calculate_hooks(uac_r->dialog)<0 && !uac_r->dialog->hooks.next_hop)
+	if ((nhtype = w_calculate_hooks(uac_r->dialog)) < 0)
+		/* if err's returned, the message is incorrect */
 		goto error2;
 		goto error2;
 
 
 	if (!uac_r->dialog->loc_seq.is_set) {
 	if (!uac_r->dialog->loc_seq.is_set) {
@@ -257,6 +259,10 @@ static inline int t_uac_prepare(uac_req_t *uac_r,
 		new_cell->flags |= T_IS_INVITE_FLAG;
 		new_cell->flags |= T_IS_INVITE_FLAG;
 		new_cell->flags|=T_AUTO_INV_100 &
 		new_cell->flags|=T_AUTO_INV_100 &
 				(!cfg_get(tm, tm_cfg, tm_auto_inv_100) -1);
 				(!cfg_get(tm, tm_cfg, tm_auto_inv_100) -1);
+#ifdef WITH_AS_SUPPORT
+		if (uac_r->cb_flags & TMCB_DONT_ACK)
+			new_cell->flags |= T_NO_AUTO_ACK;
+#endif
 		lifetime=cfg_get(tm, tm_cfg, tm_max_inv_lifetime);
 		lifetime=cfg_get(tm, tm_cfg, tm_max_inv_lifetime);
 	}else
 	}else
 		lifetime=cfg_get(tm, tm_cfg, tm_max_noninv_lifetime);
 		lifetime=cfg_get(tm, tm_cfg, tm_max_noninv_lifetime);
@@ -282,8 +288,8 @@ static inline int t_uac_prepare(uac_req_t *uac_r,
 	set_kr(REQ_FWDED);
 	set_kr(REQ_FWDED);
 
 
 	request = &new_cell->uac[0].request;
 	request = &new_cell->uac[0].request;
-	
 	request->dst = dst;
 	request->dst = dst;
+	request->flags |= nhtype;
 
 
 	if (!is_ack) {
 	if (!is_ack) {
 #ifdef TM_DEL_UNREF
 #ifdef TM_DEL_UNREF
@@ -402,18 +408,7 @@ void send_prepared_request(struct retr_buf *request)
  */
  */
 int t_uac(uac_req_t *uac_r)
 int t_uac(uac_req_t *uac_r)
 {
 {
-	struct retr_buf *request;
-	struct cell *cell;
-	int ret;
-	int is_ack;
-
-	ret = t_uac_prepare(uac_r, &request, &cell);
-	if (ret < 0) return ret;
-	is_ack = (uac_r->method->len == 3) && (memcmp("ACK", uac_r->method->s, 3)==0) ? 1 : 0;
-	send_prepared_request_impl(request, !is_ack /* retransmit */);
-	if (cell && is_ack)
-		free_cell(cell);
-	return ret;
+	return t_uac_with_ids(uac_r, NULL, NULL);
 }
 }
 
 
 /*
 /*
@@ -445,6 +440,140 @@ int t_uac_with_ids(uac_req_t *uac_r,
 	return ret;
 	return ret;
 }
 }
 
 
+#ifdef WITH_AS_SUPPORT
+struct retr_buf *local_ack_rb(sip_msg_t *rpl_2xx, struct cell *trans,
+					unsigned int branch, str *hdrs, str *body)
+{
+	struct retr_buf *lack;
+	unsigned int buf_len;
+	char *buffer;
+	struct dest_info dst;
+
+	buf_len = (unsigned)sizeof(struct retr_buf);
+	if (! (buffer = build_dlg_ack(rpl_2xx, trans, branch, hdrs, body, 
+			&buf_len, &dst))) {
+		return 0;
+	} else {
+		/* 'buffer' now points into a contiguous chunk of memory with enough
+		 * room to hold both the retr. buffer and the string raw buffer: it
+		 * points to the begining of the string buffer; we iterate back to get
+		 * the begining of the space for the retr. buffer. */
+		lack = &((struct retr_buf *)buffer)[-1];
+		lack->buffer = buffer;
+		lack->buffer_len = buf_len;
+		lack->dst = dst;
+	}
+
+	/* TODO: need next 2? */
+	lack->activ_type = TYPE_LOCAL_ACK;
+	lack->my_T = trans;
+
+	return lack;
+}
+
+void free_local_ack(struct retr_buf *lack)
+{
+	shm_free(lack);
+}
+
+void free_local_ack_unsafe(struct retr_buf *lack)
+{
+	shm_free_unsafe(lack);
+}
+
+/**
+ * @return: 
+ * 	0: success
+ * 	-1: internal error
+ * 	-2: insane call :)
+ */
+int ack_local_uac(struct cell *trans, str *hdrs, str *body)
+{
+	struct retr_buf *local_ack, *old_lack;
+	int ret;
+
+	/* sanity checks */
+
+#ifdef EXTRA_DEBUG
+	if (! trans) {
+		BUG("no transaction to ACK.\n");
+		abort();
+	}
+#endif
+
+#define RET_INVALID \
+		ret = -2; \
+		goto fin
+
+	if (! is_local(trans)) {
+		ERR("trying to ACK non local transaction (T@%p).\n", trans);
+		RET_INVALID;
+	}
+	if (! is_invite(trans)) {
+		ERR("trying to ACK non INVITE local transaction (T@%p).\n", trans);
+		RET_INVALID;
+	}
+	if (! trans->uac[0].reply) {
+		ERR("trying to ACK un-completed INVITE transaction (T@%p).\n", trans);
+		RET_INVALID;
+	}
+
+	if (! (trans->flags & T_NO_AUTO_ACK)) {
+		ERR("trying to ACK an auto-ACK transaction (T@%p).\n", trans);
+		RET_INVALID;
+	}
+	if (trans->uac[0].local_ack) {
+		ERR("trying to rebuild ACK retransmission buffer (T@%p).\n", trans);
+		RET_INVALID;
+	}
+
+	/* looks sane: build the retransmission buffer */
+
+	if (! (local_ack = local_ack_rb(trans->uac[0].reply, trans, /*branch*/0, 
+			hdrs, body))) {
+		ERR("failed to build ACK retransmission buffer");
+		RET_INVALID;
+	} else {
+		/* set the new buffer, but only if not already set (conc. invok.) */
+		if ((old_lack = (struct retr_buf *)atomic_cmpxchg_long(
+				(void *)&trans->uac[0].local_ack, 0, (long)local_ack))) {
+			/* buffer already set: deny current attempt */
+			ERR("concurrent ACKing for local INVITE detected (T@%p).\n",trans);
+			free_local_ack(local_ack);
+			RET_INVALID;
+		}
+	}
+
+	if (msg_send(&local_ack->dst, local_ack->buffer, local_ack->buffer_len)<0){
+		/* hopefully will succeed on next 2xx retransmission */
+		ERR("failed to send local ACK (T@%p).\n", trans);
+		ret = -1;
+		goto fin;
+	}
+#ifdef	TMCB_ONSEND
+	else {
+		run_onsend_callbacks2(TMCB_REQUEST_SENT, &trans->uac[0]->request, 
+				local_ack->buffer, local_ack->buffer_len, &local_ack->dst,
+				TYPE_LOCAL_ACK);
+	}
+#endif
+
+	ret = 0;
+fin:
+	/* TODO: ugly! */
+	/* FIXME: the T had been obtain by t_lookup_ident()'ing for it, so, it is
+	 * ref-counted. The t_unref() can not be used, as it requests a valid SIP
+	 * message (all available might be the reply, but if AS goes wrong and
+	 * tries to ACK before the final reply is received, we still have to
+	 * lookup the T to find this out). */
+	UNREF( trans );
+	return ret;
+
+#undef RET_INVALID
+}
+#endif /* WITH_AS_SUPPORT */
+
+
 /*
 /*
  * Send a message within a dialog
  * Send a message within a dialog
  */
  */

+ 16 - 0
modules/tm/uac.h

@@ -82,6 +82,9 @@ typedef int (*req_t)(uac_req_t *uac_r, str* ruri, str* to, str* from, str *next_
 typedef int (*t_uac_t)(uac_req_t *uac_r);
 typedef int (*t_uac_t)(uac_req_t *uac_r);
 typedef int (*t_uac_with_ids_t)(uac_req_t *uac_r,
 typedef int (*t_uac_with_ids_t)(uac_req_t *uac_r,
 		unsigned int *ret_index, unsigned int *ret_label);
 		unsigned int *ret_index, unsigned int *ret_label);
+#ifdef WITH_AS_SUPPORT
+typedef int (*ack_local_uac_f)(struct cell *trans, str *hdrs, str *body);
+#endif
 typedef int (*prepare_request_within_f)(uac_req_t *uac_r,
 typedef int (*prepare_request_within_f)(uac_req_t *uac_r,
 		struct retr_buf **dst_req);
 		struct retr_buf **dst_req);
 typedef void (*send_prepared_request_f)(struct retr_buf *request_dst);
 typedef void (*send_prepared_request_f)(struct retr_buf *request_dst);
@@ -121,6 +124,19 @@ int req_within(uac_req_t *uac_r);
  */
  */
 int req_outside(uac_req_t *uac_r, str* to, str* from);
 int req_outside(uac_req_t *uac_r, str* to, str* from);
 
 
+
+#ifdef WITH_AS_SUPPORT
+struct retr_buf *local_ack_rb(sip_msg_t *rpl_2xx, struct cell *trans,
+					unsigned int branch, str *hdrs, str *body);
+void free_local_ack(struct retr_buf *lack);
+void free_local_ack_unsafe(struct retr_buf *lack);
+
+/**
+ * ACK an existing local INVITE transaction...
+ */
+int ack_local_uac(struct cell *trans, str *hdrs, str *body);
+#endif
+
 /*
 /*
  * Send a transactional request, no dialogs involved
  * Send a transactional request, no dialogs involved
  */
  */