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
 #		when the servers are unreachable, and even expired resource
 #		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
 
 DEFS= $(extra_defs) \
@@ -476,6 +478,7 @@ DEFS= $(extra_defs) \
 	 -DUSE_DNS_FAILOVER \
 	 -DUSE_DST_BLACKLIST \
 	 -DUSE_NAPTR \
+	 -DWITH_AS_SUPPORT \
 	 -DDBG_QM_MALLOC \
 	 #-DUSE_DNS_CACHE_STATS \
 	 #-DUSE_DST_BLACKLIST_STATS \

+ 7 - 1
NEWS

@@ -77,7 +77,13 @@ modules:
  - blst      - new module containing script blacklist manipulations functions
                (the source of a message can be blacklisted, removed from the
                 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
                defining TM_E2E_ACK_CHECK_FROM_URI)
              - 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
+ * @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)
 {
 	str* uri;
 	struct sip_uri puri;
+	int nhop;
 
 	/* we might re-calc. some existing hooks =>
 	 * 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;
 			_d->hooks.next_hop = &_d->route_set->nameaddr.uri;
 			_d->hooks.first_route = _d->route_set;
+			nhop = F_RB_NH_LOOSE;
 		} else {
 			_d->hooks.request_uri = &_d->route_set->nameaddr.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;
 			else 
 				_d->hooks.last_route = NULL; /* ? */
+			nhop = F_RB_NH_STRICT;
 		}
 	} else {
 		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;
 		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.last_route = 0; 
+		_d->hooks.last_route = 0;
 	}
 
 	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);
 	}
 
-	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;
 		}
 
-		calculate_hooks(_d);
+		if (calculate_hooks(_d) < 0)
+			return -1;
 	}
 
 	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;
 		}
 
-		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 (calculate_hooks(_d)) {
+	if (calculate_hooks(_d) < 0) {
 		LOG(L_ERR, "set_dlg_target(): Error while calculating hooks\n");
 		return -1;
 	}

+ 4 - 0
modules/tm/h_table.c

@@ -65,6 +65,7 @@
 #include "h_table.h"
 #include "fix_lumps.h" /* free_via_clen_lump */
 #include "timer.h"
+#include "uac.h" /* free_local_ack */
 
 
 static enum kill_reason kr;
@@ -174,6 +175,9 @@ void free_cell( struct cell* dead_cell )
 #endif
 	}
 
+	if (dead_cell->uac[0].local_ack)
+		free_local_ack_unsafe(dead_cell->uac[0].local_ack);
+
 	/* collected to tags */
 	tt=dead_cell->fwded_totags;
 	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_CANCELED	0x40 /* rb/branch canceled */
 #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 */
@@ -154,10 +158,10 @@ typedef struct retr_buf
 	short activ_type;
 	/* set to status code if the buffer is a reply,
 	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 */
 	unsigned short branch; /* no more then 65k branches :-) */
-	short   buffer_len;
+	short buffer_len;
 	char *buffer;
 	/*the cell that contains this retrans_buff*/
 	struct cell* my_T;
@@ -207,6 +211,16 @@ typedef struct ua_client
 	str              uri;
 	/* if we don't store, we at least want to know the status */
 	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;
 
 
@@ -237,6 +251,10 @@ struct totag_elem {
 #define T_IN_AGONY (1<<5) /* set if waiting to die (delete timer)
                              TODO: replace it with del on unref */
 #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)
 
 /* 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 )
 {
 	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;
 	/* insert at the beginning */
 	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_E2ECANCEL_IN_N     16
 #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
-#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
-#define TMCB_MAX_N              17
+#define TMCB_MAX_N              18
 #endif
 
 
@@ -103,6 +106,9 @@ struct cell;
 #define TMCB_DESTROY          (1<<TMCB_DESTROY_N)
 #define TMCB_E2ECANCEL_IN     (1<<TMCB_E2ECANCEL_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
 #define TMCB_REQUEST_SENT      (1<<TMCB_REQUEST_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
  *  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
 	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;
 }
 
+#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, 
 					unsigned int label)
 {
@@ -1512,7 +1537,11 @@ int t_lookup_ident(struct cell ** trans, unsigned int 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]);
 	/* all the transactions from the entry are compared */
 	clist_foreach(hash_bucket, p_cell, next_c){

+ 504 - 77
modules/tm/t_msgbuilder.c

@@ -50,7 +50,9 @@
 
 #include "defs.h"
 
-
+#ifdef EXTRA_DEBUG
+#include <assert.h>
+#endif
 #include "../../comp_defs.h"
 #include "../../hash_func.h"
 #include "../../globals.h"
@@ -375,10 +377,13 @@ error:
 }
 
 
-struct rte {
+typedef struct rte {
 	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;
-};
+} rte_t;
 
   	 
 static inline void free_rte_list(struct rte* list)
@@ -388,74 +393,13 @@ static inline void free_rte_list(struct rte* list)
 	while(list) {
 		ptr = list;
 		list = list->next;
+		if (ptr->free_rr)
+			free_rr(&ptr->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)
 {
 	struct rte* ptr;
@@ -547,7 +491,429 @@ static inline int get_contact_uri(struct sip_msg* msg, str* uri)
 	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
@@ -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)
       */
 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;
 	unsigned int via_len;
@@ -568,18 +934,47 @@ char *build_dlg_ack(struct sip_msg* rpl, struct cell *Trans,
 	struct rte* list;
 	str contact, ruri, *cont;
 	str next_hop;
+	str body_len;
+	str _to, *to = &_to;
 #ifdef USE_DNS_FAILOVER
 	struct dns_srv_handle dns_h;
 #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;
+	} 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;
 	}
 	
+	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)) {
 		     /* contact != ruri means that the next
 		      * 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 */
 	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 */
-	*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);
+#endif
 	if (!req_buf) {
-		LOG(L_ERR, "build_dlg_ack: Cannot allocate memory\n");
+		ERR("Cannot allocate memory (%u+1)\n", *len);
 		goto error01;
 	}
 	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);
 	}
 	
-	     /* 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;
 	
 	pkg_free(via);
@@ -692,7 +1119,7 @@ char *build_dlg_ack(struct sip_msg* rpl, struct cell *Trans,
  error:
 	free_rte_list(list);
 	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);
 
 #ifdef EXTRA_DEBUG
-	if (w-buf != *len ) abort();
+	assert(w-buf == *len);
 #endif
 
 	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
  */
 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 "../../hash_func.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,
 								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 */
 	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 */
 		Trans->uac[branch].last_received=new_code;
-		*should_store=0;
 		*should_relay= new_code==100? -1 : branch;
 		if (new_code>=200 ) {
 			which_cancel( Trans, cancel_bitmap );
@@ -1818,7 +1855,9 @@ int reply_received( struct sip_msg  *p_msg )
 													&onsend_params);
 					}
 #endif
+#ifndef WITH_AS_SUPPORT
 					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
 	struct dns_srv_handle dns_h;
 #endif
+	long nhtype;
 
 	ret=-1;
 	hi=0; /* make gcc happy */
@@ -203,7 +204,8 @@ static inline int t_uac_prepare(uac_req_t *uac_r,
 	/*** added by dcm 
 	 * - 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;
 
 	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_AUTO_INV_100 &
 				(!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);
 	}else
 		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);
 
 	request = &new_cell->uac[0].request;
-	
 	request->dst = dst;
+	request->flags |= nhtype;
 
 	if (!is_ack) {
 #ifdef TM_DEL_UNREF
@@ -402,18 +408,7 @@ void send_prepared_request(struct retr_buf *request)
  */
 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;
 }
 
+#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
  */

+ 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_with_ids_t)(uac_req_t *uac_r,
 		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,
 		struct retr_buf **dst_req);
 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);
 
+
+#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
  */