Ticket #205: merged proxy functionalities from stable to trunk

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@1127 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjsip-apps/build/Samples-vc.mak b/pjsip-apps/build/Samples-vc.mak
index c4f0d6c..18d5d65 100644
--- a/pjsip-apps/build/Samples-vc.mak
+++ b/pjsip-apps/build/Samples-vc.mak
@@ -52,6 +52,8 @@
 	  $(BINDIR)\sipstateless.exe \
 	  $(BINDIR)\sndinfo.exe \
 	  $(BINDIR)\sndtest.exe \
+	  $(BINDIR)\stateful_proxy.exe \
+	  $(BINDIR)\stateless_proxy.exe \
 	  $(BINDIR)\streamutil.exe \
 	  $(BINDIR)\tonegen.exe
 
diff --git a/pjsip-apps/build/Samples.mak b/pjsip-apps/build/Samples.mak
index a7eb5ad..66113fd 100644
--- a/pjsip-apps/build/Samples.mak
+++ b/pjsip-apps/build/Samples.mak
@@ -57,6 +57,8 @@
 	   sipstateless \
 	   sndinfo \
 	   sndtest \
+	   stateful_proxy \
+	   stateless_proxy \
 	   streamutil \
 	   tonegen
 
diff --git a/pjsip-apps/build/samples.dsp b/pjsip-apps/build/samples.dsp
index f637360..91e5fb7 100644
--- a/pjsip-apps/build/samples.dsp
+++ b/pjsip-apps/build/samples.dsp
@@ -154,6 +154,14 @@
 # End Source File

 # Begin Source File

 

+SOURCE=..\src\samples\stateful_proxy.c

+# End Source File

+# Begin Source File

+

+SOURCE=..\src\samples\stateless_proxy.c

+# End Source File

+# Begin Source File

+

 SOURCE=..\src\samples\streamutil.c

 # End Source File

 # Begin Source File

@@ -166,6 +174,10 @@
 # PROP Default_Filter "h;hpp;hxx;hm;inl"

 # Begin Source File

 

+SOURCE=..\src\samples\proxy.h

+# End Source File

+# Begin Source File

+

 SOURCE=..\src\samples\util.h

 # End Source File

 # End Group

diff --git a/pjsip-apps/build/samples.vcproj b/pjsip-apps/build/samples.vcproj
index 422f06c..986dacb 100644
--- a/pjsip-apps/build/samples.vcproj
+++ b/pjsip-apps/build/samples.vcproj
@@ -4,6 +4,7 @@
 	Version="8.00"

 	Name="samples"

 	ProjectGUID="{E378A1FC-0C9C-4462-860F-7E60BC1BF84E}"

+	RootNamespace="samples"

 	Keyword="MakeFileProj"

 	>

 	<Platforms>

@@ -137,6 +138,14 @@
 				>

 			</File>

 			<File

+				RelativePath="..\src\samples\stateful_proxy.c"

+				>

+			</File>

+			<File

+				RelativePath="..\src\samples\stateless_proxy.c"

+				>

+			</File>

+			<File

 				RelativePath="..\src\samples\streamutil.c"

 				>

 			</File>

@@ -150,6 +159,10 @@
 			Filter="h;hpp;hxx;hm;inl"

 			>

 			<File

+				RelativePath="..\src\samples\proxy.h"

+				>

+			</File>

+			<File

 				RelativePath="..\src\samples\util.h"

 				>

 			</File>

diff --git a/pjsip-apps/src/pjsua/pjsua_app.c b/pjsip-apps/src/pjsua/pjsua_app.c
index 53b9641..ab72f84 100644
--- a/pjsip-apps/src/pjsua/pjsua_app.c
+++ b/pjsip-apps/src/pjsua/pjsua_app.c
@@ -2056,7 +2056,6 @@
     pj_str_t str_method;
     pjsip_method method;
     pjsip_tx_data *tdata;
-    pjsua_acc_info acc_info;
     pjsip_endpoint *endpt;
     pj_status_t status;
 
@@ -2065,15 +2064,7 @@
     str_method = pj_str(cstr_method);
     pjsip_method_init_np(&method, &str_method);
 
-    pjsua_acc_get_info(current_acc, &acc_info);
-
-    status = pjsip_endpt_create_request(endpt, &method, dst_uri, 
-					&acc_info.acc_uri, dst_uri,
-					NULL, NULL, -1, NULL, &tdata);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to create request", status);
-	return;
-    }
+    status = pjsua_acc_create_request(current_acc, &method, dst_uri, &tdata);
 
     status = pjsip_endpt_send_request(endpt, tdata, -1, NULL, NULL);
     if (status != PJ_SUCCESS) {
diff --git a/pjsip-apps/src/samples/proxy.h b/pjsip-apps/src/samples/proxy.h
new file mode 100644
index 0000000..5f62a50
--- /dev/null
+++ b/pjsip-apps/src/samples/proxy.h
@@ -0,0 +1,525 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2007 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#include <pjsip.h>
+#include <pjlib-util.h>
+#include <pjlib.h>
+
+
+/* Options */
+static struct global_struct
+{
+    pj_caching_pool	 cp;
+    pjsip_endpoint	*endpt;
+    int			 port;
+    pj_pool_t		*pool;
+
+    pj_thread_t		*thread;
+    pj_bool_t		 quit_flag;
+
+    pj_bool_t		 record_route;
+
+    unsigned		 name_cnt;
+    pjsip_host_port	 name[16];
+} global;
+
+
+
+static void app_perror(const char *msg, pj_status_t status)
+{
+    char errmsg[PJ_ERR_MSG_SIZE];
+
+    pj_strerror(status, errmsg, sizeof(errmsg));
+    PJ_LOG(1,(THIS_FILE, "%s: %s", msg, errmsg));
+}
+
+
+static void usage(void)
+{
+    puts("Options:\n"
+	 "\n"
+	 " --port N       Set local listener port to N\n"
+	 " --rr           Perform record routing\n"
+	 " --log-level N  Set log level to N (default: 4)\n"
+	 " --help         Show this help screen\n"
+	 );
+}
+
+
+static pj_status_t init_options(int argc, char *argv[])
+{
+    struct pj_getopt_option long_opt[] = {
+	{ "port",	1, 0, 'p'},
+	{ "rr",		0, 0, 'R'},
+	{ "log-level",	1, 0, 'L'},
+	{ "help",	0, 0, 'h'},
+    };
+    int c;
+    int opt_ind;
+
+    pj_optind = 0;
+    while((c=pj_getopt_long(argc, argv, "", long_opt, &opt_ind))!=-1) {
+	switch (c) {
+	case 'p':
+	    global.port = atoi(pj_optarg);
+	    printf("Port is set to %d\n", global.pool);
+	    break;
+	
+	case 'R':
+	    global.record_route = PJ_TRUE;
+	    printf("Using record route mode\n");
+	    break;
+
+	case 'L':
+	    pj_log_set_level(atoi(pj_optarg));
+	    break;
+
+	case 'h':
+	    usage();
+	    return -1;
+
+	default:
+	    puts("Unknown option. Run with --help for help.");
+	    return -1;
+	}
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/*****************************************************************************
+ * This is a very simple PJSIP module, whose sole purpose is to display
+ * incoming and outgoing messages to log. This module will have priority
+ * higher than transport layer, which means:
+ *
+ *  - incoming messages will come to this module first before reaching
+ *    transaction layer.
+ *
+ *  - outgoing messages will come to this module last, after the message
+ *    has been 'printed' to contiguous buffer by transport layer and
+ *    appropriate transport instance has been decided for this message.
+ *
+ */
+
+/* Notification on incoming messages */
+static pj_bool_t logging_on_rx_msg(pjsip_rx_data *rdata)
+{
+    PJ_LOG(5,(THIS_FILE, "RX %d bytes %s from %s %s:%d:\n"
+			 "%.*s\n"
+			 "--end msg--",
+			 rdata->msg_info.len,
+			 pjsip_rx_data_get_info(rdata),
+			 rdata->tp_info.transport->type_name,
+			 rdata->pkt_info.src_name,
+			 rdata->pkt_info.src_port,
+			 (int)rdata->msg_info.len,
+			 rdata->msg_info.msg_buf));
+    
+    /* Always return false, otherwise messages will not get processed! */
+    return PJ_FALSE;
+}
+
+/* Notification on outgoing messages */
+static pj_status_t logging_on_tx_msg(pjsip_tx_data *tdata)
+{
+    
+    /* Important note:
+     *	tp_info field is only valid after outgoing messages has passed
+     *	transport layer. So don't try to access tp_info when the module
+     *	has lower priority than transport layer.
+     */
+
+    PJ_LOG(5,(THIS_FILE, "TX %d bytes %s to %s %s:%d:\n"
+			 "%.*s\n"
+			 "--end msg--",
+			 (tdata->buf.cur - tdata->buf.start),
+			 pjsip_tx_data_get_info(tdata),
+			 tdata->tp_info.transport->type_name,
+			 tdata->tp_info.dst_name,
+			 tdata->tp_info.dst_port,
+			 (int)(tdata->buf.cur - tdata->buf.start),
+			 tdata->buf.start));
+
+    /* Always return success, otherwise message will not get sent! */
+    return PJ_SUCCESS;
+}
+
+/* The module instance. */
+static pjsip_module mod_msg_logger = 
+{
+    NULL, NULL,				/* prev, next.		*/
+    { "mod-msg-logger", 14 },		/* Name.		*/
+    -1,					/* Id			*/
+    PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority	        */
+    NULL,				/* load()		*/
+    NULL,				/* start()		*/
+    NULL,				/* stop()		*/
+    NULL,				/* unload()		*/
+    &logging_on_rx_msg,			/* on_rx_request()	*/
+    &logging_on_rx_msg,			/* on_rx_response()	*/
+    &logging_on_tx_msg,			/* on_tx_request.	*/
+    &logging_on_tx_msg,			/* on_tx_response()	*/
+    NULL,				/* on_tsx_state()	*/
+
+};
+
+
+static pj_status_t init_stack(void)
+{
+    pj_status_t status;
+
+    /* Must init PJLIB first: */
+    status = pj_init();
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
+    /* Then init PJLIB-UTIL: */
+    status = pjlib_util_init();
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
+    /* Must create a pool factory before we can allocate any memory. */
+    pj_caching_pool_init(&global.cp, &pj_pool_factory_default_policy, 0);
+
+    /* Create the endpoint: */
+    status = pjsip_endpt_create(&global.cp.factory, NULL, &global.endpt);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+    /* Init transaction layer for stateful proxy only */
+#if STATEFUL
+    status = pjsip_tsx_layer_init_module(global.endpt);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+#endif
+
+    /* Create listening transport */
+    {
+	pj_sockaddr_in addr;
+
+	addr.sin_family = PJ_AF_INET;
+	addr.sin_addr.s_addr = 0;
+	addr.sin_port = pj_htons((pj_uint16_t)global.port);
+
+	status = pjsip_udp_transport_start( global.endpt, &addr, 
+					    NULL, 1, NULL);
+	if (status != PJ_SUCCESS)
+	    return status;
+    }
+
+    /* Create pool for the application */
+    global.pool = pj_pool_create(&global.cp.factory, "proxyapp", 
+				 4000, 4000, NULL);
+
+    /* Register the logger module */
+    pjsip_endpt_register_module(global.endpt, &mod_msg_logger);
+
+    return PJ_SUCCESS;
+}
+
+
+static pj_status_t init_proxy(void)
+{
+    pj_in_addr addr;
+    unsigned i;
+
+    /* List all names matching local endpoint.
+     * Note that PJLIB version 0.6 and newer has a function to
+     * enumerate local IP interface (pj_enum_ip_interface()), so
+     * by using it would be possible to list all IP interfaces in
+     * this host.
+     */
+
+    /* The first address is important since this would be the one
+     * to be added in Record-Route.
+     */
+    if (pj_gethostip(&addr) == PJ_SUCCESS) {
+	pj_strdup2(global.pool, &global.name[global.name_cnt].host,
+		   pj_inet_ntoa(addr));
+	global.name[global.name_cnt].port = global.port;
+	global.name_cnt++;
+    }
+
+    global.name[global.name_cnt].host = pj_str("127.0.0.1");
+    global.name[global.name_cnt].port = global.port;
+    global.name_cnt++;
+
+    global.name[global.name_cnt].host = *pj_gethostname();
+    global.name[global.name_cnt].port = global.port;
+    global.name_cnt++;
+
+    global.name[global.name_cnt].host = pj_str("localhost");
+    global.name[global.name_cnt].port = global.port;
+    global.name_cnt++;
+
+    PJ_LOG(3,(THIS_FILE, "Proxy started, listening on port %d", global.port));
+    PJ_LOG(3,(THIS_FILE, "Local host aliases:"));
+    for (i=0; i<global.name_cnt; ++i) {
+	PJ_LOG(3,(THIS_FILE, " %.*s:%d", 
+		  (int)global.name[i].host.slen,
+		  global.name[i].host.ptr,
+		  global.name[i].port));
+    }
+
+    if (global.record_route) {
+	PJ_LOG(3,(THIS_FILE, "Using Record-Route mode"));
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+#if PJ_HAS_THREADS
+static int worker_thread(void *p)
+{
+    pj_time_val delay = {0, 10};
+
+    PJ_UNUSED_ARG(p);
+
+    while (!global.quit_flag) {
+	pjsip_endpt_handle_events(global.endpt, &delay);
+    }
+
+    return 0;
+}
+#endif
+
+
+/* Utility to determine if URI is local to this host. */
+static pj_bool_t is_uri_local(const pjsip_sip_uri *uri)
+{
+    unsigned i;
+    for (i=0; i<global.name_cnt; ++i) {
+	if ((uri->port == global.name[i].port ||
+	     (uri->port==0 && global.name[i].port==5060)) &&
+	    pj_stricmp(&uri->host, &global.name[i].host)==0)
+	{
+	    /* Match */
+	    return PJ_TRUE;
+	}
+    }
+
+    /* Doesn't match */
+    return PJ_FALSE;
+}
+
+
+/* Proxy utility to verify incoming requests.
+ * Return non-zero if verification failed.
+ */
+static pj_status_t proxy_verify_request(pjsip_rx_data *rdata)
+{
+    const pj_str_t STR_PROXY_REQUIRE = {"Proxy-Require", 13};
+
+    /* RFC 3261 Section 16.3 Request Validation */
+
+    /* Before an element can proxy a request, it MUST verify the message's
+     * validity.  A valid message must pass the following checks:
+     * 
+     * 1. Reasonable Syntax
+     * 2. URI scheme
+     * 3. Max-Forwards
+     * 4. (Optional) Loop Detection
+     * 5. Proxy-Require
+     * 6. Proxy-Authorization
+     */
+
+    /* 1. Reasonable Syntax.
+     * This would have been checked by transport layer.
+     */
+
+    /* 2. URI scheme.
+     * We only want to support "sip:" URI scheme for this simple proxy.
+     */
+    if (!PJSIP_URI_SCHEME_IS_SIP(rdata->msg_info.msg->line.req.uri)) {
+	pjsip_endpt_respond_stateless(global.endpt, rdata, 
+				      PJSIP_SC_UNSUPPORTED_URI_SCHEME, NULL,
+				      NULL, NULL);
+	return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_UNSUPPORTED_URI_SCHEME);
+    }
+
+    /* 3. Max-Forwards.
+     * Send error if Max-Forwards is 1 or lower.
+     */
+    if (rdata->msg_info.max_fwd && rdata->msg_info.max_fwd->ivalue <= 1) {
+	pjsip_endpt_respond_stateless(global.endpt, rdata, 
+				      PJSIP_SC_TOO_MANY_HOPS, NULL,
+				      NULL, NULL);
+	return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_TOO_MANY_HOPS);
+    }
+
+    /* 4. (Optional) Loop Detection.
+     * Nah, we don't do that with this simple proxy.
+     */
+
+    /* 5. Proxy-Require */
+    if (pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_PROXY_REQUIRE, 
+				   NULL) != NULL) 
+    {
+	pjsip_endpt_respond_stateless(global.endpt, rdata, 
+				      PJSIP_SC_BAD_EXTENSION, NULL,
+				      NULL, NULL);
+	return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_EXTENSION);
+    }
+
+    /* 6. Proxy-Authorization.
+     * Nah, we don't require any authorization with this sample.
+     */
+
+    return PJ_SUCCESS;
+}
+
+
+/* Process route information in the reqeust */
+static pj_status_t proxy_process_routing(pjsip_tx_data *tdata)
+{
+    pjsip_sip_uri *target;
+    pjsip_route_hdr *hroute;
+
+    /* RFC 3261 Section 16.4 Route Information Preprocessing */
+
+    target = (pjsip_sip_uri*) tdata->msg->line.req.uri;
+
+    /* The proxy MUST inspect the Request-URI of the request.  If the
+     * Request-URI of the request contains a value this proxy previously
+     * placed into a Record-Route header field (see Section 16.6 item 4),
+     * the proxy MUST replace the Request-URI in the request with the last
+     * value from the Route header field, and remove that value from the
+     * Route header field.  The proxy MUST then proceed as if it received
+     * this modified request.
+     */
+    /* Nah, we don't want to support this */
+
+    /* If the Request-URI contains a maddr parameter, the proxy MUST check
+     * to see if its value is in the set of addresses or domains the proxy
+     * is configured to be responsible for.  If the Request-URI has a maddr
+     * parameter with a value the proxy is responsible for, and the request
+     * was received using the port and transport indicated (explicitly or by
+     * default) in the Request-URI, the proxy MUST strip the maddr and any
+     * non-default port or transport parameter and continue processing as if
+     * those values had not been present in the request.
+     */
+    if (target->maddr_param.slen != 0) {
+	pjsip_sip_uri maddr_uri;
+
+	maddr_uri.host = target->maddr_param;
+	maddr_uri.port = global.port;
+
+	if (is_uri_local(&maddr_uri)) {
+	    target->maddr_param.slen = 0;
+	    target->port = 0;
+	    target->transport_param.slen = 0;
+	}
+    }
+
+    /* If the first value in the Route header field indicates this proxy,
+     * the proxy MUST remove that value from the request.
+     */
+    hroute = (pjsip_route_hdr*) 
+	      pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, NULL);
+    if (hroute && is_uri_local((pjsip_sip_uri*)hroute->name_addr.uri)) {
+	pj_list_erase(hroute);
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/* Postprocess the request before forwarding it */
+static void proxy_postprocess(pjsip_tx_data *tdata)
+{
+    /* Optionally record-route */
+    if (global.record_route) {
+	char uribuf[128];
+	pj_str_t uri;
+	const pj_str_t H_RR = { "Record-Route", 12 };
+	pjsip_generic_string_hdr *rr;
+
+	pj_ansi_snprintf(uribuf, sizeof(uribuf), "<sip:%.*s:%d;lr>",
+			 (int)global.name[0].host.slen,
+			 global.name[0].host.ptr,
+			 global.name[0].port);
+	uri = pj_str(uribuf);
+	rr = pjsip_generic_string_hdr_create(tdata->pool,
+					     &H_RR, &uri);
+	pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)rr);
+    }
+}
+
+
+/* Calculate new target for the request */
+static pj_status_t proxy_calculate_target(pjsip_rx_data *rdata,
+					  pjsip_tx_data *tdata)
+{
+    pjsip_sip_uri *target;
+
+    /* RFC 3261 Section 16.5 Determining Request Targets */
+
+    target = (pjsip_sip_uri*) tdata->msg->line.req.uri;
+
+    /* If the Request-URI of the request contains an maddr parameter, the
+     * Request-URI MUST be placed into the target set as the only target
+     * URI, and the proxy MUST proceed to Section 16.6.
+     */
+    if (target->maddr_param.slen) {
+	proxy_postprocess(tdata);
+	return PJ_SUCCESS;
+    }
+
+
+    /* If the domain of the Request-URI indicates a domain this element is
+     * not responsible for, the Request-URI MUST be placed into the target
+     * set as the only target, and the element MUST proceed to the task of
+     * Request Forwarding (Section 16.6).
+     */
+    if (!is_uri_local(target)) {
+	proxy_postprocess(tdata);
+	return PJ_SUCCESS;
+    }
+
+    /* If the target set for the request has not been predetermined as
+     * described above, this implies that the element is responsible for the
+     * domain in the Request-URI, and the element MAY use whatever mechanism
+     * it desires to determine where to send the request. 
+     */
+
+    /* We're not interested to receive request destined to us, so
+     * respond with 404/Not Found (only if request is not ACK!).
+     */
+    if (rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD) {
+	pjsip_endpt_respond_stateless(global.endpt, rdata,
+				      PJSIP_SC_NOT_FOUND, NULL,
+				      NULL, NULL);
+    }
+
+    /* Delete the request since we're not forwarding it */
+    pjsip_tx_data_dec_ref(tdata);
+
+    return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_FOUND);
+}
+
+
+/* Destroy stack */
+static void destroy_stack(void)
+{
+    pjsip_endpt_destroy(global.endpt);
+    pj_pool_release(global.pool);
+    pj_caching_pool_destroy(&global.cp);
+
+    pj_shutdown();
+}
+
diff --git a/pjsip-apps/src/samples/stateful_proxy.c b/pjsip-apps/src/samples/stateful_proxy.c
new file mode 100644
index 0000000..d83ec91
--- /dev/null
+++ b/pjsip-apps/src/samples/stateful_proxy.c
@@ -0,0 +1,580 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2007 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#define THIS_FILE   "stateful_proxy.c"
+
+/* Common proxy functions */
+#define STATEFUL    1
+#include "proxy.h"
+
+
+/*
+ * mod_stateful_proxy is the module to receive SIP request and
+ * response message that is outside any transaction context.
+ */
+static pj_bool_t proxy_on_rx_request(pjsip_rx_data *rdata );
+static pj_bool_t proxy_on_rx_response(pjsip_rx_data *rdata );
+
+static pjsip_module mod_stateful_proxy =
+{
+    NULL, NULL,				/* prev, next.		*/
+    { "mod-stateful-proxy", 18 },	/* Name.		*/
+    -1,					/* Id			*/
+    PJSIP_MOD_PRIORITY_UA_PROXY_LAYER,	/* Priority		*/
+    NULL,				/* load()		*/
+    NULL,				/* start()		*/
+    NULL,				/* stop()		*/
+    NULL,				/* unload()		*/
+    &proxy_on_rx_request,		/* on_rx_request()	*/
+    &proxy_on_rx_response,		/* on_rx_response()	*/
+    NULL,				/* on_tx_request.	*/
+    NULL,				/* on_tx_response()	*/
+    NULL,				/* on_tsx_state()	*/
+};
+
+
+/*
+ * mod_tu (tu=Transaction User) is the module to receive notification
+ * from transaction when the transaction state has changed.
+ */
+static void tu_on_tsx_state(pjsip_transaction *tsx, pjsip_event *event);
+
+static pjsip_module mod_tu =
+{
+    NULL, NULL,				/* prev, next.		*/
+    { "mod-transaction-user", 20 },	/* Name.		*/
+    -1,					/* Id			*/
+    PJSIP_MOD_PRIORITY_APPLICATION,	/* Priority		*/
+    NULL,				/* load()		*/
+    NULL,				/* start()		*/
+    NULL,				/* stop()		*/
+    NULL,				/* unload()		*/
+    NULL,				/* on_rx_request()	*/
+    NULL,				/* on_rx_response()	*/
+    NULL,				/* on_tx_request.	*/
+    NULL,				/* on_tx_response()	*/
+    &tu_on_tsx_state,			/* on_tsx_state()	*/
+};
+
+
+/* This is the data that is attached to the UAC transaction */
+struct uac_data
+{
+    pjsip_transaction	*uas_tsx;
+    pj_timer_entry	 timer;
+};
+
+
+/* This is the data that is attached to the UAS transaction */
+struct uas_data
+{
+    pjsip_transaction	*uac_tsx;
+};
+
+
+
+static pj_status_t init_stateful_proxy(void)
+{
+    pj_status_t status;
+
+    status = pjsip_endpt_register_module( global.endpt, &mod_stateful_proxy);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+    status = pjsip_endpt_register_module( global.endpt, &mod_tu);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+    return PJ_SUCCESS;
+}
+
+
+/* Callback to be called to handle new incoming requests. */
+static pj_bool_t proxy_on_rx_request( pjsip_rx_data *rdata )
+{
+    pjsip_transaction *uas_tsx, *uac_tsx;
+    struct uac_data *uac_data;
+    struct uas_data *uas_data;
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+
+    if (rdata->msg_info.msg->line.req.method.id != PJSIP_CANCEL_METHOD) {
+
+	/* Verify incoming request */
+	status = proxy_verify_request(rdata);
+	if (status != PJ_SUCCESS) {
+	    app_perror("RX invalid request", status);
+	    return PJ_TRUE;
+	}
+
+	/*
+	 * Request looks sane, next clone the request to create transmit data.
+	 */
+	status = pjsip_endpt_create_request_fwd(global.endpt, rdata, NULL,
+						NULL, 0, &tdata);
+	if (status != PJ_SUCCESS) {
+	    pjsip_endpt_respond_stateless(global.endpt, rdata,
+					  PJSIP_SC_INTERNAL_SERVER_ERROR, 
+					  NULL, NULL, NULL);
+	    return PJ_TRUE;
+	}
+
+
+	/* Process routing */
+	status = proxy_process_routing(tdata);
+	if (status != PJ_SUCCESS) {
+	    app_perror("Error processing route", status);
+	    return PJ_TRUE;
+	}
+
+	/* Calculate target */
+	status = proxy_calculate_target(rdata, tdata);
+	if (status != PJ_SUCCESS) {
+	    app_perror("Error calculating target", status);
+	    return PJ_TRUE;
+	}
+
+	/* Everything is set to forward the request. */
+
+	/* If this is an ACK request, forward statelessly.
+	 * This happens if the proxy records route and this ACK
+	 * is sent for 2xx response. An ACK that is sent for non-2xx
+	 * final response will be absorbed by transaction layer, and
+	 * it will not be received by on_rx_request() callback.
+	 */
+	if (tdata->msg->line.req.method.id == PJSIP_ACK_METHOD) {
+	    status = pjsip_endpt_send_request_stateless(global.endpt, tdata, 
+							NULL, NULL);
+	    if (status != PJ_SUCCESS) {
+		app_perror("Error forwarding request", status);
+		return PJ_TRUE;
+	    }
+
+	    return PJ_TRUE;
+	}
+
+	/* Create UAC transaction for forwarding the request. 
+	 * Set our module as the transaction user to receive further
+	 * events from this transaction.
+	 */
+	status = pjsip_tsx_create_uac(&mod_tu, tdata, &uac_tsx);
+	if (status != PJ_SUCCESS) {
+	    pjsip_tx_data_dec_ref(tdata);
+	    pjsip_endpt_respond_stateless(global.endpt, rdata, 
+					  PJSIP_SC_INTERNAL_SERVER_ERROR, 
+					  NULL, NULL, NULL);
+	    return PJ_TRUE;
+	}
+
+	/* Create UAS transaction to handle incoming request */
+	status = pjsip_tsx_create_uas(&mod_tu, rdata, &uas_tsx);
+	if (status != PJ_SUCCESS) {
+	    pjsip_tx_data_dec_ref(tdata);
+	    pjsip_endpt_respond_stateless(global.endpt, rdata, 
+					  PJSIP_SC_INTERNAL_SERVER_ERROR, 
+					  NULL, NULL, NULL);
+	    pjsip_tsx_terminate(uac_tsx, PJSIP_SC_INTERNAL_SERVER_ERROR);
+	    return PJ_TRUE;
+	}
+
+	/* Feed the request to the UAS transaction to drive it's state 
+	 * out of NULL state. 
+	 */
+	pjsip_tsx_recv_msg(uas_tsx, rdata);
+
+	/* Attach a data to the UAC transaction, to be used to find the
+	 * UAS transaction when we receive response in the UAC side.
+	 */
+	uac_data = (struct uac_data*)
+		   pj_pool_alloc(uac_tsx->pool, sizeof(struct uac_data));
+	uac_data->uas_tsx = uas_tsx;
+	uac_tsx->mod_data[mod_tu.id] = (void*)uac_data;
+
+	/* Attach data to the UAS transaction, to find the UAC transaction
+	 * when cancelling INVITE request.
+	 */
+	uas_data = (struct uas_data*)
+		    pj_pool_alloc(uas_tsx->pool, sizeof(struct uas_data));
+	uas_data->uac_tsx = uac_tsx;
+	uas_tsx->mod_data[mod_tu.id] = (void*)uas_data;
+
+	/* Everything is setup, forward the request */
+	status = pjsip_tsx_send_msg(uac_tsx, tdata);
+	if (status != PJ_SUCCESS) {
+	    pjsip_tx_data *err_res;
+
+	    /* Fail to send request, for some reason */
+
+	    /* Destroy transmit data */
+	    pjsip_tx_data_dec_ref(tdata);
+
+	    /* I think UAC transaction should have been destroyed when
+	     * it fails to send request, so no need to destroy it.
+	    pjsip_tsx_terminate(uac_tsx, PJSIP_SC_INTERNAL_SERVER_ERROR);
+	     */
+
+	    /* Send 500/Internal Server Error to UAS transaction */
+	    pjsip_endpt_create_response(global.endpt, rdata,
+					500, NULL, &err_res);
+	    pjsip_tsx_send_msg(uas_tsx, err_res);
+
+	    return PJ_TRUE;
+	}
+
+	/* Send 100/Trying if this is an INVITE */
+	if (rdata->msg_info.msg->line.req.method.id == PJSIP_INVITE_METHOD) {
+	    pjsip_tx_data *res100;
+
+	    pjsip_endpt_create_response(global.endpt, rdata, 100, NULL, 
+					&res100);
+	    pjsip_tsx_send_msg(uas_tsx, res100);
+	}
+
+    } else {
+	/* This is CANCEL request */
+	pjsip_transaction *invite_uas;
+	struct uas_data *uas_data;
+	pj_str_t key;
+	
+	/* Find the UAS INVITE transaction */
+	pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_UAS_ROLE,
+			     &pjsip_invite_method, rdata);
+	invite_uas = pjsip_tsx_layer_find_tsx(&key, PJ_TRUE);
+	if (!invite_uas) {
+	    /* Invite transaction not found, respond CANCEL with 481 */
+	    pjsip_endpt_respond_stateless(global.endpt, rdata, 481, NULL,
+					  NULL, NULL);
+	    return PJ_TRUE;
+	}
+
+	/* Respond 200 OK to CANCEL */
+	pjsip_endpt_respond(global.endpt, NULL, rdata, 200, NULL, NULL,
+			    NULL, NULL);
+
+	/* Send CANCEL to cancel the UAC transaction.
+	 * The UAS INVITE transaction will get final response when
+	 * we receive final response from the UAC INVITE transaction.
+	 */
+	uas_data = (struct uas_data*) invite_uas->mod_data[mod_tu.id];
+	if (uas_data->uac_tsx) {
+	    pjsip_tx_data *cancel;
+
+	    pj_mutex_lock(uas_data->uac_tsx->mutex);
+
+	    pjsip_endpt_create_cancel(global.endpt, uas_data->uac_tsx->last_tx,
+				      &cancel);
+	    pjsip_endpt_send_request(global.endpt, cancel, -1, NULL, NULL);
+
+	    pj_mutex_unlock(uas_data->uac_tsx->mutex);
+	}
+
+	/* Unlock UAS tsx because it is locked in find_tsx() */
+	pj_mutex_unlock(invite_uas->mutex);
+    }
+
+    return PJ_TRUE;
+}
+
+
+/* Callback to be called to handle incoming response outside
+ * any transactions. This happens for example when 2xx/OK
+ * for INVITE is received and transaction will be destroyed
+ * immediately, so we need to forward the subsequent 2xx/OK
+ * retransmission statelessly.
+ */
+static pj_bool_t proxy_on_rx_response( pjsip_rx_data *rdata )
+{
+    pjsip_tx_data *tdata;
+    pjsip_response_addr res_addr;
+    pjsip_via_hdr *hvia;
+    pj_status_t status;
+
+    /* Create response to be forwarded upstream (Via will be stripped here) */
+    status = pjsip_endpt_create_response_fwd(global.endpt, rdata, 0, &tdata);
+    if (status != PJ_SUCCESS) {
+	app_perror("Error creating response", status);
+	return PJ_TRUE;
+    }
+
+    /* Get topmost Via header */
+    hvia = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
+    if (hvia == NULL) {
+	/* Invalid response! Just drop it */
+	pjsip_tx_data_dec_ref(tdata);
+	return PJ_TRUE;
+    }
+
+    /* Calculate the address to forward the response */
+    pj_bzero(&res_addr, sizeof(res_addr));
+    res_addr.dst_host.type = PJSIP_TRANSPORT_UDP;
+    res_addr.dst_host.flag = 
+	pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_UDP);
+
+    /* Destination address is Via's received param */
+    res_addr.dst_host.addr.host = hvia->recvd_param;
+    if (res_addr.dst_host.addr.host.slen == 0) {
+	/* Someone has messed up our Via header! */
+	res_addr.dst_host.addr.host = hvia->sent_by.host;
+    }
+
+    /* Destination port is the rport */
+    if (hvia->rport_param != 0 && hvia->rport_param != -1)
+	res_addr.dst_host.addr.port = hvia->rport_param;
+
+    if (res_addr.dst_host.addr.port == 0) {
+	/* Ugh, original sender didn't put rport!
+	 * At best, can only send the response to the port in Via.
+	 */
+	res_addr.dst_host.addr.port = hvia->sent_by.port;
+    }
+
+    /* Forward response */
+    status = pjsip_endpt_send_response(global.endpt, &res_addr, tdata,
+				       NULL, NULL);
+    if (status != PJ_SUCCESS) {
+	app_perror("Error forwarding response", status);
+	return PJ_TRUE;
+    }
+
+    return PJ_TRUE;
+}
+
+
+/* Callback to be called to handle transaction state changed. */
+static void tu_on_tsx_state(pjsip_transaction *tsx, pjsip_event *event)
+{
+    struct uac_data *uac_data;
+    pj_status_t status;
+
+    if (tsx->role == PJSIP_ROLE_UAS) {
+	if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+	    struct uas_data *uas_data;
+
+	    uas_data = (struct uas_data*) tsx->mod_data[mod_tu.id];
+	    if (uas_data->uac_tsx) {
+		uac_data = (struct uac_data*)
+			   uas_data->uac_tsx->mod_data[mod_tu.id];
+		uac_data->uas_tsx = NULL;
+	    }
+		       
+	}
+	return;
+    }
+
+    /* Get the data that we attached to the UAC transaction previously */
+    uac_data = (struct uac_data*) tsx->mod_data[mod_tu.id];
+
+
+    /* Handle incoming response */
+    if (event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
+
+	pjsip_rx_data *rdata;
+	pjsip_response_addr res_addr;
+        pjsip_via_hdr *hvia;
+	pjsip_tx_data *tdata;
+
+	rdata = event->body.tsx_state.src.rdata;
+
+	/* Do not forward 100 response for INVITE (we already responded
+	 * INVITE with 100)
+	 */
+	if (tsx->method.id == PJSIP_INVITE_METHOD && 
+	    rdata->msg_info.msg->line.status.code == 100)
+	{
+	    return;
+	}
+
+	/* Create response to be forwarded upstream 
+	 * (Via will be stripped here) 
+	 */
+	status = pjsip_endpt_create_response_fwd(global.endpt, rdata, 0, 
+						 &tdata);
+	if (status != PJ_SUCCESS) {
+	    app_perror("Error creating response", status);
+	    return;
+	}
+
+	/* Get topmost Via header of the new response */
+	hvia = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, 
+						   NULL);
+	if (hvia == NULL) {
+	    /* Invalid response! Just drop it */
+	    pjsip_tx_data_dec_ref(tdata);
+	    return;
+	}
+
+	/* Calculate the address to forward the response */
+	pj_bzero(&res_addr, sizeof(res_addr));
+	res_addr.dst_host.type = PJSIP_TRANSPORT_UDP;
+	res_addr.dst_host.flag = 
+	    pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_UDP);
+
+	/* Destination address is Via's received param */
+	res_addr.dst_host.addr.host = hvia->recvd_param;
+	if (res_addr.dst_host.addr.host.slen == 0) {
+	    /* Someone has messed up our Via header! */
+	    res_addr.dst_host.addr.host = hvia->sent_by.host;
+	}
+
+	/* Destination port is the rport */
+	if (hvia->rport_param != 0 && hvia->rport_param != -1)
+	    res_addr.dst_host.addr.port = hvia->rport_param;
+
+	if (res_addr.dst_host.addr.port == 0) {
+	    /* Ugh, original sender didn't put rport!
+	     * At best, can only send the response to the port in Via.
+	     */
+	    res_addr.dst_host.addr.port = hvia->sent_by.port;
+	}
+
+	/* Forward response with the UAS transaction */
+	pjsip_tsx_send_msg(uac_data->uas_tsx, tdata);
+
+    }
+
+    /* If UAC transaction is terminated, terminate the UAS as well.
+     * This could happen because of:
+     *	- timeout on the UAC side
+     *  - receipt of 2xx response to INVITE
+     */
+    if (tsx->state == PJSIP_TSX_STATE_TERMINATED && uac_data->uas_tsx) {
+
+	pjsip_transaction *uas_tsx;
+	struct uas_data *uas_data;
+
+	uas_tsx = uac_data->uas_tsx;
+	uas_data = (struct uas_data*) uas_tsx->mod_data[mod_tu.id];
+	uas_data->uac_tsx = NULL;
+
+	if (event->body.tsx_state.type == PJSIP_EVENT_TIMER) {
+
+	    /* Send 408/Timeout if this is an INVITE transaction, since
+	     * we must have sent provisional response before. For non
+	     * INVITE transaction, just destroy it.
+	     */
+	    if (tsx->method.id == PJSIP_INVITE_METHOD) {
+
+		pjsip_tx_data *tdata = uas_tsx->last_tx;
+
+		tdata->msg->line.status.code = PJSIP_SC_REQUEST_TIMEOUT;
+		tdata->msg->line.status.reason = pj_str("Request timed out");
+		tdata->msg->body = NULL;
+
+		pjsip_tx_data_add_ref(tdata);
+		pjsip_tx_data_invalidate_msg(tdata);
+
+		pjsip_tsx_send_msg(uas_tsx, tdata);
+
+	    } else {
+		/* For non-INVITE, just destroy the UAS transaction */
+		pjsip_tsx_terminate(uas_tsx, PJSIP_SC_REQUEST_TIMEOUT);
+	    }
+
+	} else if (event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
+
+	    if (uas_tsx->state < PJSIP_TSX_STATE_TERMINATED) {
+		pjsip_msg *msg;
+		int code;
+
+		msg = event->body.tsx_state.src.rdata->msg_info.msg;
+		code = msg->line.status.code;
+
+		uac_data->uas_tsx = NULL;
+		pjsip_tsx_terminate(uas_tsx, code);
+	    }
+	}
+    }
+}
+
+
+/*
+ * main()
+ */
+int main(int argc, char *argv[])
+{
+    pj_status_t status;
+
+    global.port = 5060;
+    global.record_route = 0;
+
+    pj_log_set_level(4);
+
+    status = init_options(argc, argv);
+    if (status != PJ_SUCCESS)
+	return 1;
+
+    status = init_stack();
+    if (status != PJ_SUCCESS) {
+	app_perror("Error initializing stack", status);
+	return 1;
+    }
+
+    status = init_proxy();
+    if (status != PJ_SUCCESS) {
+	app_perror("Error initializing proxy", status);
+	return 1;
+    }
+
+    status = init_stateful_proxy();
+    if (status != PJ_SUCCESS) {
+	app_perror("Error initializing stateful proxy", status);
+	return 1;
+    }
+
+#if PJ_HAS_THREADS
+    status = pj_thread_create(global.pool, "sproxy", &worker_thread, 
+			      NULL, 0, 0, &global.thread);
+    if (status != PJ_SUCCESS) {
+	app_perror("Error creating thread", status);
+	return 1;
+    }
+
+    while (!global.quit_flag) {
+	char line[10];
+
+	puts("\n"
+	     "Menu:\n"
+	     "  q    quit\n"
+	     "  d    dump status\n"
+	     "  dd   dump detailed status\n"
+	     "");
+
+	fgets(line, sizeof(line), stdin);
+
+	if (line[0] == 'q') {
+	    global.quit_flag = PJ_TRUE;
+	} else if (line[0] == 'd') {
+	    pj_bool_t detail = (line[1] == 'd');
+	    pjsip_endpt_dump(global.endpt, detail);
+	    pjsip_tsx_layer_dump(detail);
+	}
+    }
+
+    pj_thread_join(global.thread);
+
+#else
+    puts("\nPress Ctrl-C to quit\n");
+    for (;;) {
+	pj_time_val delay = {0, 0};
+	pjsip_endpt_handle_events(global.endpt, &delay);
+    }
+#endif
+
+    destroy_stack();
+
+    return 0;
+}
+
diff --git a/pjsip-apps/src/samples/stateless_proxy.c b/pjsip-apps/src/samples/stateless_proxy.c
new file mode 100644
index 0000000..f40b263
--- /dev/null
+++ b/pjsip-apps/src/samples/stateless_proxy.c
@@ -0,0 +1,250 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2007 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#define THIS_FILE   "stateless_proxy.c"
+
+/* Common proxy functions */
+#define STATEFUL    0
+#include "proxy.h"
+
+
+/* Callback to be called to handle incoming requests. */
+static pj_bool_t on_rx_request( pjsip_rx_data *rdata );
+
+/* Callback to be called to handle incoming response. */
+static pj_bool_t on_rx_response( pjsip_rx_data *rdata );
+
+
+static pj_status_t init_stateless_proxy(void)
+{
+    static pjsip_module mod_stateless_proxy =
+    {
+	NULL, NULL,			    /* prev, next.	*/
+	{ "mod-stateless-proxy", 19 },	    /* Name.		*/
+	-1,				    /* Id		*/
+	PJSIP_MOD_PRIORITY_UA_PROXY_LAYER,  /* Priority		*/
+	NULL,				    /* load()		*/
+	NULL,				    /* start()		*/
+	NULL,				    /* stop()		*/
+	NULL,				    /* unload()		*/
+	&on_rx_request,			    /* on_rx_request()	*/
+	&on_rx_response,		    /* on_rx_response()	*/
+	NULL,				    /* on_tx_request.	*/
+	NULL,				    /* on_tx_response()	*/
+	NULL,				    /* on_tsx_state()	*/
+    };
+
+    pj_status_t status;
+
+    /* Register our module to receive incoming requests. */
+    status = pjsip_endpt_register_module( global.endpt, &mod_stateless_proxy);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+    return PJ_SUCCESS;
+}
+
+
+/* Callback to be called to handle incoming requests. */
+static pj_bool_t on_rx_request( pjsip_rx_data *rdata )
+{
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+
+
+    /* Verify incoming request */
+    status = proxy_verify_request(rdata);
+    if (status != PJ_SUCCESS) {
+	app_perror("RX invalid request", status);
+	return PJ_TRUE;
+    }
+
+    /*
+     * Request looks sane, next clone the request to create transmit data.
+     */
+    status = pjsip_endpt_create_request_fwd(global.endpt, rdata, NULL,
+					    NULL, 0, &tdata);
+    if (status != PJ_SUCCESS) {
+	pjsip_endpt_respond_stateless(global.endpt, rdata,
+				      PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, 
+				      NULL, NULL);
+	return PJ_TRUE;
+    }
+
+
+    /* Process routing */
+    status = proxy_process_routing(tdata);
+    if (status != PJ_SUCCESS) {
+	app_perror("Error processing route", status);
+	return PJ_TRUE;
+    }
+
+    /* Calculate target */
+    status = proxy_calculate_target(rdata, tdata);
+    if (status != PJ_SUCCESS) {
+	app_perror("Error calculating target", status);
+	return PJ_TRUE;
+    }
+
+    /* Target is set, forward the request */
+    status = pjsip_endpt_send_request_stateless(global.endpt, tdata, 
+						NULL, NULL);
+    if (status != PJ_SUCCESS) {
+	app_perror("Error forwarding request", status);
+	return PJ_TRUE;
+    }
+
+    return PJ_TRUE;
+}
+
+
+/* Callback to be called to handle incoming response. */
+static pj_bool_t on_rx_response( pjsip_rx_data *rdata )
+{
+    pjsip_tx_data *tdata;
+    pjsip_response_addr res_addr;
+    pjsip_via_hdr *hvia;
+    pj_status_t status;
+
+    /* Create response to be forwarded upstream (Via will be stripped here) */
+    status = pjsip_endpt_create_response_fwd(global.endpt, rdata, 0, &tdata);
+    if (status != PJ_SUCCESS) {
+	app_perror("Error creating response", status);
+	return PJ_TRUE;
+    }
+
+    /* Get topmost Via header */
+    hvia = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
+    if (hvia == NULL) {
+	/* Invalid response! Just drop it */
+	pjsip_tx_data_dec_ref(tdata);
+	return PJ_TRUE;
+    }
+
+    /* Calculate the address to forward the response */
+    pj_bzero(&res_addr, sizeof(res_addr));
+    res_addr.dst_host.type = PJSIP_TRANSPORT_UDP;
+    res_addr.dst_host.flag = pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_UDP);
+
+    /* Destination address is Via's received param */
+    res_addr.dst_host.addr.host = hvia->recvd_param;
+    if (res_addr.dst_host.addr.host.slen == 0) {
+	/* Someone has messed up our Via header! */
+	res_addr.dst_host.addr.host = hvia->sent_by.host;
+    }
+
+    /* Destination port is the rpot */
+    if (hvia->rport_param != 0 && hvia->rport_param != -1)
+	res_addr.dst_host.addr.port = hvia->rport_param;
+
+    if (res_addr.dst_host.addr.port == 0) {
+	/* Ugh, original sender didn't put rport!
+	 * At best, can only send the response to the port in Via.
+	 */
+	res_addr.dst_host.addr.port = hvia->sent_by.port;
+    }
+
+    /* Forward response */
+    status = pjsip_endpt_send_response(global.endpt, &res_addr, tdata, 
+				       NULL, NULL);
+    if (status != PJ_SUCCESS) {
+	app_perror("Error forwarding response", status);
+	return PJ_TRUE;
+    }
+
+    return PJ_TRUE;
+}
+
+
+/*
+ * main()
+ */
+int main(int argc, char *argv[])
+{
+    pj_status_t status;
+
+    global.port = 5060;
+    pj_log_set_level(4);
+
+    status = init_options(argc, argv);
+    if (status != PJ_SUCCESS)
+	return 1;
+
+    status = init_stack();
+    if (status != PJ_SUCCESS) {
+	app_perror("Error initializing stack", status);
+	return 1;
+    }
+
+    status = init_proxy();
+    if (status != PJ_SUCCESS) {
+	app_perror("Error initializing proxy", status);
+	return 1;
+    }
+
+    status = init_stateless_proxy();
+    if (status != PJ_SUCCESS) {
+	app_perror("Error initializing stateless proxy", status);
+	return 1;
+    }
+
+#if PJ_HAS_THREADS
+    status = pj_thread_create(global.pool, "sproxy", &worker_thread, 
+			      NULL, 0, 0, &global.thread);
+    if (status != PJ_SUCCESS) {
+	app_perror("Error creating thread", status);
+	return 1;
+    }
+
+    while (!global.quit_flag) {
+	char line[10];
+
+	puts("\n"
+	     "Menu:\n"
+	     "  q    quit\n"
+	     "  d    dump status\n"
+	     "  dd   dump detailed status\n"
+	     "");
+
+	fgets(line, sizeof(line), stdin);
+
+	if (line[0] == 'q') {
+	    global.quit_flag = PJ_TRUE;
+	} else if (line[0] == 'd') {
+	    pj_bool_t detail = (line[1] == 'd');
+	    pjsip_endpt_dump(global.endpt, detail);
+#if STATEFUL
+	    pjsip_tsx_layer_dump(detail);
+#endif
+	}
+    }
+
+    pj_thread_join(global.thread);
+
+#else
+    puts("\nPress Ctrl-C to quit\n");
+    for (;;) {
+	pj_time_val delay = {0, 0};
+	pjsip_endpt_handle_events(global.endpt, &delay);
+    }
+#endif
+
+    destroy_stack();
+
+    return 0;
+}
+
diff --git a/pjsip/include/pjsip/sip_msg.h b/pjsip/include/pjsip/sip_msg.h
index 6c7b020..7a30352 100644
--- a/pjsip/include/pjsip/sip_msg.h
+++ b/pjsip/include/pjsip/sip_msg.h
@@ -710,6 +710,19 @@
  */
 PJ_DECL(pjsip_msg*)  pjsip_msg_create( pj_pool_t *pool, pjsip_msg_type_e type);
 
+
+/**
+ * Perform a deep clone of a SIP message.
+ *
+ * @param pool	    The pool for creating the new message.
+ * @param msg	    The message to be duplicated.
+ *
+ * @return	    New message, which is duplicated from the original 
+ *		    message.
+ */
+PJ_DECL(pjsip_msg*) pjsip_msg_clone( pj_pool_t *pool, const pjsip_msg *msg);
+
+
 /** 
  * Find a header in the message by the header type.
  *
diff --git a/pjsip/include/pjsip/sip_util.h b/pjsip/include/pjsip/sip_util.h
index 4d19d30..62fb847 100644
--- a/pjsip/include/pjsip/sip_util.h
+++ b/pjsip/include/pjsip/sip_util.h
@@ -472,27 +472,49 @@
 					       void (*cb)(void*,pjsip_event*));
 
 /**
+ * @}
+ */
+
+/**
+ * @defgroup PJSIP_PROXY_CORE Core Proxy Layer
+ * @ingroup PJSIP
+ * @brief Core proxy operations
+ * @{
+ */
+
+/**
  * Create new request message to be forwarded upstream to new destination URI 
  * in uri. The new request is a full/deep clone of the request received in 
  * rdata, unless if other copy mechanism is specified in the options. 
  * The branch parameter, if not NULL, will be used as the branch-param in 
  * the Via header. If it is NULL, then a unique branch parameter will be used.
  *
+ * Note: this function DOES NOT perform Route information preprocessing as
+ *	  described in RFC 3261 Section 16.4. Application must take care of
+ *	  removing/updating the Route headers according of the rules as
+ *	  described in that section.
+ *
  * @param endpt	    The endpoint instance.
  * @param rdata	    The incoming request message.
  * @param uri	    The URI where the request will be forwarded to.
- * @param branch    Optional branch parameter.
+ * @param branch    Optional branch parameter. Application may specify its
+ *		    own branch, for example if it wishes to perform loop
+ *		    detection. If the branch parameter is not specified,
+ *		    this function will generate its own by calling 
+ *		    #pjsip_calculate_branch_id() function.
  * @param options   Optional option flags when duplicating the message.
  * @param tdata	    The result.
  *
  * @return	    PJ_SUCCESS on success.
  */
-PJ_DECL(pj_status_t) pjsip_endpt_create_request_fwd( pjsip_endpoint *endpt,
-						     pjsip_rx_data *rdata, 
-						     const pjsip_uri *uri,
-						     const pj_str_t *branch,
-						     unsigned options,
-						     pjsip_tx_data **tdata);
+PJ_DECL(pj_status_t) pjsip_endpt_create_request_fwd(pjsip_endpoint *endpt,
+						    pjsip_rx_data *rdata, 
+						    const pjsip_uri *uri,
+						    const pj_str_t *branch,
+						    unsigned options,
+						    pjsip_tx_data **tdata);
+
+
 
 /**
  * Create new response message to be forwarded downstream by the proxy from 
@@ -515,13 +537,17 @@
 						      pjsip_tx_data **tdata);
 
 
+
 /**
  * Create a globally unique branch parameter based on the information in 
- * the incoming request message. This function guarantees that subsequent 
- * retransmissions of the same request will generate the same branch id.
- * This function can also be used in the loop detection process. 
- * If the same request arrives back in the proxy with the same URL, it will
- * calculate into the same branch id.
+ * the incoming request message, for the purpose of creating a new request
+ * for forwarding. This is the default implementation used by 
+ * #pjsip_endpt_create_request_fwd() function if the branch parameter is
+ * not specified.
+ *
+ * The default implementation here will just create an MD5 hash of the
+ * top-most Via.
+ *
  * Note that the returned string was allocated from rdata's pool.
  *
  * @param rdata	    The incoming request message.
diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h
index fee08d3..b6ba7d5 100644
--- a/pjsip/include/pjsua-lib/pjsua.h
+++ b/pjsip/include/pjsua-lib/pjsua.h
@@ -2201,6 +2201,25 @@
 
 
 /**
+ * Create arbitrary requests using the account. Application should only use
+ * this function to create auxiliary requests outside dialog, such as
+ * OPTIONS, and use the call or presence API to create dialog related
+ * requests.
+ *
+ * @param acc_id	The account ID.
+ * @param method	The SIP method of the request.
+ * @param target	Target URI.
+ * @param p_tdata	Pointer to receive the request.
+ *
+ * @return		PJ_SUCCESS or the error code.
+ */
+PJ_DECL(pj_status_t) pjsua_acc_create_request(pjsua_acc_id acc_id,
+					      const pjsip_method *method,
+					      const pj_str_t *target,
+					      pjsip_tx_data **p_tdata);
+
+
+/**
  * Create a suitable URI to be put as Contact based on the specified
  * target URI for the specified account.
  *
diff --git a/pjsip/src/pjsip/sip_msg.c b/pjsip/src/pjsip/sip_msg.c
index 66304bd..47c493d 100644
--- a/pjsip/src/pjsip/sip_msg.c
+++ b/pjsip/src/pjsip/sip_msg.c
@@ -247,6 +247,38 @@
     return msg;
 }
 
+PJ_DEF(pjsip_msg*) pjsip_msg_clone( pj_pool_t *pool, const pjsip_msg *src)
+{
+    pjsip_msg *dst;
+    const pjsip_hdr *sh;
+
+    dst = pjsip_msg_create(pool, src->type);
+
+    /* Clone request/status line */
+    if (src->type == PJSIP_REQUEST_MSG) {
+	pjsip_method_copy(pool, &dst->line.req.method, &src->line.req.method);
+	dst->line.req.uri = pjsip_uri_clone(pool, src->line.req.uri);
+    } else {
+	dst->line.status.code = src->line.status.code;
+	pj_strdup(pool, &dst->line.status.reason, &src->line.status.reason);
+    }
+
+    /* Clone headers */
+    sh = src->hdr.next;
+    while (sh != &src->hdr) {
+	pjsip_hdr *dh = pjsip_hdr_clone(pool, sh);
+	pjsip_msg_add_hdr(dst, dh);
+	sh = sh->next;
+    }
+
+    /* Clone message body */
+    if (src->body) {
+	dst->body = pjsip_msg_body_clone(pool, src->body);
+    }
+
+    return dst;
+}
+
 PJ_DEF(void*)  pjsip_msg_find_hdr( const pjsip_msg *msg, 
 				   pjsip_hdr_e hdr_type, const void *start)
 {
diff --git a/pjsip/src/pjsip/sip_util.c b/pjsip/src/pjsip/sip_util.c
index e95faae..3f1add4 100644
--- a/pjsip/src/pjsip/sip_util.c
+++ b/pjsip/src/pjsip/sip_util.c
@@ -1037,6 +1037,10 @@
     /* Check arguments. */
     PJ_ASSERT_RETURN(pool && rdata && res_addr, PJ_EINVAL);
 
+    /* rdata must be a request message! */
+    PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
+		     PJ_EINVAL);
+
     /* All requests must have "received" parameter.
      * This must always be done in transport layer.
      */
diff --git a/pjsip/src/pjsip/sip_util_proxy.c b/pjsip/src/pjsip/sip_util_proxy.c
index 73d1c4b..b963139 100644
--- a/pjsip/src/pjsip/sip_util_proxy.c
+++ b/pjsip/src/pjsip/sip_util_proxy.c
@@ -17,50 +17,339 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
  */
 #include <pjsip/sip_util.h>
+#include <pjsip/sip_endpoint.h>
 #include <pjsip/sip_errno.h>
+#include <pjsip/sip_msg.h>
 #include <pj/assert.h>
+#include <pj/ctype.h>
+#include <pj/except.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pjlib-util/md5.h>
 
-PJ_DEF(pj_status_t) pjsip_endpt_create_request_fwd(  pjsip_endpoint *endpt,
-						     pjsip_rx_data *rdata, 
-						     const pjsip_uri *uri,
-						     const pj_str_t *branch,
-						     unsigned options,
-						     pjsip_tx_data **tdata)
+
+/**
+ * Clone the incoming SIP request or response message. A forwarding proxy
+ * typically would need to clone the incoming SIP message before processing
+ * the message.
+ *
+ * Once a transmit data is created, the reference counter is initialized to 1.
+ *
+ * @param endpt	    The endpoint instance.
+ * @param rdata	    The incoming SIP message.
+ * @param p_tdata   Pointer to receive the transmit data containing
+ *		    the duplicated message.
+ *
+ * @return	    PJ_SUCCESS on success.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_clone_msg( pjsip_endpoint *endpt,
+					   const pjsip_rx_data *rdata,
+					   pjsip_tx_data **p_tdata)
 {
-    PJ_UNUSED_ARG(endpt);
-    PJ_UNUSED_ARG(rdata);
-    PJ_UNUSED_ARG(uri);
-    PJ_UNUSED_ARG(branch);
-    PJ_UNUSED_ARG(options);
-    PJ_UNUSED_ARG(tdata);
+    pjsip_tx_data *tdata;
+    pj_status_t status;
 
-    pj_assert(!"Not implemented yet");
-    return PJ_EBUG;
+    status = pjsip_endpt_create_tdata(endpt, &tdata);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    tdata->msg = pjsip_msg_clone(tdata->pool, rdata->msg_info.msg);
+
+    pjsip_tx_data_add_ref(tdata);
+    
+    *p_tdata = tdata;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Create new request message to be forwarded upstream to new destination URI 
+ * in uri. 
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_create_request_fwd(pjsip_endpoint *endpt,
+						   pjsip_rx_data *rdata, 
+						   const pjsip_uri *uri,
+						   const pj_str_t *branch,
+						   unsigned options,
+						   pjsip_tx_data **p_tdata)
+{
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+    PJ_USE_EXCEPTION;
+
+
+    PJ_ASSERT_RETURN(endpt && rdata && p_tdata, PJ_EINVAL);
+    PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG, 
+		     PJSIP_ENOTREQUESTMSG);
+
+    PJ_UNUSED_ARG(options);
+
+
+    /* Request forwarding rule in RFC 3261 section 16.6:
+     *
+     * For each target, the proxy forwards the request following these
+     * steps:
+     * 
+     * 1.  Make a copy of the received request
+     * 2.  Update the Request-URI
+     * 3.  Update the Max-Forwards header field
+     * 4.  Optionally add a Record-route header field value
+     * 5.  Optionally add additional header fields
+     * 6.  Postprocess routing information
+     * 7.  Determine the next-hop address, port, and transport
+     * 8.  Add a Via header field value
+     * 9.  Add a Content-Length header field if necessary
+     * 10. Forward the new request
+     *
+     * Of these steps, we only do step 1-3, since the later will be
+     * done by application.
+     */
+
+    status = pjsip_endpt_create_tdata(endpt, &tdata);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Always increment ref counter to 1 */
+    pjsip_tx_data_add_ref(tdata);
+
+    /* Duplicate the request */
+    PJ_TRY {
+	pjsip_msg *dst;
+	const pjsip_msg *src = rdata->msg_info.msg;
+	const pjsip_hdr *hsrc;
+
+	/* Create the request */
+	tdata->msg = dst = pjsip_msg_create(tdata->pool, PJSIP_REQUEST_MSG);
+
+	/* Duplicate request method */
+	pjsip_method_copy(tdata->pool, &tdata->msg->line.req.method,
+			  &src->line.req.method);
+
+	/* Set request URI */
+	if (uri) {
+	    dst->line.req.uri = pjsip_uri_clone(tdata->pool, uri);
+	} else {
+	    dst->line.req.uri = pjsip_uri_clone(tdata->pool, src->line.req.uri);
+	}
+
+	/* Clone ALL headers */
+	hsrc = src->hdr.next;
+	while (hsrc != &src->hdr) {
+
+	    pjsip_hdr *hdst;
+
+	    /* If this is the top-most Via header, insert our own before
+	     * cloning the header.
+	     */
+	    if (hsrc == (pjsip_hdr*)rdata->msg_info.via) {
+		pjsip_via_hdr *hvia;
+		hvia = pjsip_via_hdr_create(tdata->pool);
+		if (branch)
+		    pj_strdup(tdata->pool, &hvia->branch_param, branch);
+		else {
+		    pj_str_t new_branch = pjsip_calculate_branch_id(rdata);
+		    pj_strdup(tdata->pool, &hvia->branch_param, &new_branch);
+		}
+		pjsip_msg_add_hdr(dst, (pjsip_hdr*)hvia);
+
+	    }
+	    /* Skip Content-Type and Content-Length as these would be 
+	     * generated when the the message is printed.
+	     */
+	    else if (hsrc->type == PJSIP_H_CONTENT_LENGTH ||
+		     hsrc->type == PJSIP_H_CONTENT_TYPE) {
+
+		hsrc = hsrc->next;
+		continue;
+
+	    }
+#if 0
+	    /* If this is the top-most Route header and it indicates loose
+	     * route, remove the header.
+	     */
+	    else if (hsrc == (pjsip_hdr*)rdata->msg_info.route) {
+
+		const pjsip_route_hdr *hroute = (const pjsip_route_hdr*) hsrc;
+		const pjsip_sip_uri *sip_uri;
+
+		if (!PJSIP_URI_SCHEME_IS_SIP(hroute->name_addr.uri) &&
+		    !PJSIP_URI_SCHEME_IS_SIPS(hroute->name_addr.uri))
+		{
+		    /* This is a bad request! */
+		    status = PJSIP_EINVALIDHDR;
+		    goto on_error;
+		}
+
+		sip_uri = (pjsip_sip_uri*) hroute->name_addr.uri;
+
+		if (sip_uri->lr_param) {
+		    /* Yes lr param is present, skip this Route header */
+		    hsrc = hsrc->next;
+		    continue;
+		}
+	    }
+#endif
+
+	    /* Clone the header */
+	    hdst = pjsip_hdr_clone(tdata->pool, hsrc);
+
+	    /* If this is Max-Forward header, decrement the value */
+	    if (hdst->type == PJSIP_H_MAX_FORWARDS) {
+		pjsip_max_fwd_hdr *hmaxfwd = (pjsip_max_fwd_hdr*)hdst;
+		--hmaxfwd->ivalue;
+	    }
+
+	    /* Append header to new request */
+	    pjsip_msg_add_hdr(dst, hdst);
+
+
+	    hsrc = hsrc->next;
+	}
+
+	/* 16.6.3:
+	 * If the copy does not contain a Max-Forwards header field, the
+         * proxy MUST add one with a field value, which SHOULD be 70.
+	 */
+	if (rdata->msg_info.max_fwd == NULL) {
+	    pjsip_max_fwd_hdr *hmaxfwd = 
+		pjsip_max_fwd_hdr_create(tdata->pool, 70);
+	    pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hmaxfwd);
+	}
+
+	/* Clone request body */
+	if (src->body) {
+	    dst->body = pjsip_msg_body_clone(tdata->pool, src->body);
+	}
+
+    }
+    PJ_CATCH_ANY {
+	status = PJ_ENOMEM;
+	goto on_error;
+    }
+    PJ_END
+
+
+    /* Done */
+    *p_tdata = tdata;
+    return PJ_SUCCESS;
+
+on_error:
+    pjsip_tx_data_dec_ref(tdata);
+    return status;
 }
 
 
 PJ_DEF(pj_status_t) pjsip_endpt_create_response_fwd( pjsip_endpoint *endpt,
 						     pjsip_rx_data *rdata, 
 						     unsigned options,
-						     pjsip_tx_data **tdata)
+						     pjsip_tx_data **p_tdata)
 {
-    PJ_UNUSED_ARG(endpt);
-    PJ_UNUSED_ARG(rdata);
-    PJ_UNUSED_ARG(options);
-    PJ_UNUSED_ARG(tdata);
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+    PJ_USE_EXCEPTION;
 
-    pj_assert(!"Not implemented yet");
-    return PJ_EBUG;
+    PJ_UNUSED_ARG(options);
+
+    status = pjsip_endpt_create_tdata(endpt, &tdata);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    pjsip_tx_data_add_ref(tdata);
+
+    PJ_TRY {
+	pjsip_msg *dst;
+	const pjsip_msg *src = rdata->msg_info.msg;
+	const pjsip_hdr *hsrc;
+
+	/* Create the request */
+	tdata->msg = dst = pjsip_msg_create(tdata->pool, PJSIP_RESPONSE_MSG);
+
+	/* Clone the status line */
+	dst->line.status.code = src->line.status.code;
+	pj_strdup(tdata->pool, &dst->line.status.reason, 
+		  &src->line.status.reason);
+
+	/* Duplicate all headers */
+	hsrc = src->hdr.next;
+	while (hsrc != &src->hdr) {
+	    
+	    /* Skip Content-Type and Content-Length as these would be 
+	     * generated when the the message is printed.
+	     */
+	    if (hsrc->type == PJSIP_H_CONTENT_LENGTH ||
+		hsrc->type == PJSIP_H_CONTENT_TYPE) {
+
+		hsrc = hsrc->next;
+		continue;
+
+	    }
+	    /* Remove the first Via header */
+	    else if (hsrc == (pjsip_hdr*) rdata->msg_info.via) {
+
+		hsrc = hsrc->next;
+		continue;
+	    }
+
+	    pjsip_msg_add_hdr(dst, pjsip_hdr_clone(tdata->pool, hsrc));
+
+	    hsrc = hsrc->next;
+	}
+
+	/* Clone message body */
+	if (src->body)
+	    dst->body = pjsip_msg_body_clone(tdata->pool, src->body);
+
+
+    }
+    PJ_CATCH_ANY {
+	status = PJ_ENOMEM;
+	goto on_error;
+    }
+    PJ_END;
+
+    *p_tdata = tdata;
+    return PJ_SUCCESS;
+
+on_error:
+    pjsip_tx_data_dec_ref(tdata);
+    return status;
+}
+
+
+static void digest2str(const unsigned char digest[], char *output)
+{
+    int i;
+    for (i = 0; i<16; ++i) {
+	pj_val_to_hex_digit(digest[i], output);
+	output += 2;
+    }
 }
 
 
 PJ_DEF(pj_str_t) pjsip_calculate_branch_id( pjsip_rx_data *rdata )
 {
-    pj_str_t empty_str = { NULL, 0 };
+    pj_md5_context ctx;
+    pj_uint8_t digest[16];
+    pj_str_t branch;
 
-    PJ_UNUSED_ARG(rdata);
-    pj_assert(!"Not implemented yet");
-    return empty_str;
+    /* Create branch ID for new request by calculating MD5 hash
+     * of the branch parameter in top-most Via header.
+     */
+    pj_md5_init(&ctx);
+    pj_md5_update(&ctx, (pj_uint8_t*)rdata->msg_info.via->branch_param.ptr,
+		  rdata->msg_info.via->branch_param.slen);
+    pj_md5_final(&ctx, digest);
+
+    branch.ptr = pj_pool_alloc(rdata->tp_info.pool, 
+			       32 + PJSIP_RFC3261_BRANCH_LEN);
+    pj_memcpy(branch.ptr, PJSIP_RFC3261_BRANCH_ID, PJSIP_RFC3261_BRANCH_LEN);
+
+    digest2str(digest, branch.ptr+PJSIP_RFC3261_BRANCH_LEN);
+
+    branch.slen = 32 + PJSIP_RFC3261_BRANCH_LEN;
+
+    return branch;
 }
 
 
diff --git a/pjsip/src/pjsua-lib/pjsua_acc.c b/pjsip/src/pjsua-lib/pjsua_acc.c
index 3e39b15..b2b6374 100644
--- a/pjsip/src/pjsua-lib/pjsua_acc.c
+++ b/pjsip/src/pjsua-lib/pjsua_acc.c
@@ -925,6 +925,45 @@
 }
 
 
+/*
+ * Create arbitrary requests for this account. 
+ */
+PJ_DEF(pj_status_t) pjsua_acc_create_request(pjsua_acc_id acc_id,
+					     const pjsip_method *method,
+					     const pj_str_t *target,
+					     pjsip_tx_data **p_tdata)
+{
+    pjsip_tx_data *tdata;
+    pjsua_acc *acc;
+    pjsip_route_hdr *r;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(method && target && p_tdata, PJ_EINVAL);
+    PJ_ASSERT_RETURN(pjsua_acc_is_valid(acc_id), PJ_EINVAL);
+
+    acc = &pjsua_var.acc[acc_id];
+
+    status = pjsip_endpt_create_request(pjsua_var.endpt, method, target, 
+					&acc->cfg.id, target,
+					NULL, NULL, -1, NULL, &tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create request", status);
+	return status;
+    }
+
+    /* Copy routeset */
+    r = acc->route_set.next;
+    while (r != &acc->route_set) {
+	pjsip_msg_add_hdr(tdata->msg, pjsip_hdr_clone(tdata->pool, r));
+	r = r->next;
+    }
+    
+    /* Done */
+    *p_tdata = tdata;
+    return PJ_SUCCESS;
+}
+
+
 PJ_DEF(pj_status_t) pjsua_acc_create_uac_contact( pj_pool_t *pool,
 						  pj_str_t *contact,
 						  pjsua_acc_id acc_id,