Squashed 'third_party/rawrtc/re/' content from commit f3163ce8b

Change-Id: I6a235e6ac0f03269d951026f9d195da05c40fdab
git-subtree-dir: third_party/rawrtc/re
git-subtree-split: f3163ce8b526a13b35ef71ce4dd6f43585064d8a
diff --git a/src/sip/addr.c b/src/sip/addr.c
new file mode 100644
index 0000000..72ab21f
--- /dev/null
+++ b/src/sip/addr.c
@@ -0,0 +1,56 @@
+/**
+ * @file sip/addr.c  SIP Address decode
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_uri.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_msg.h>
+#include <re_sip.h>
+
+
+/**
+ * Decode a pointer-length string into a SIP Address object
+ *
+ * @param addr SIP Address object
+ * @param pl   Pointer-length string
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int sip_addr_decode(struct sip_addr *addr, const struct pl *pl)
+{
+	int err;
+
+	if (!addr || !pl)
+		return EINVAL;
+
+	memset(addr, 0, sizeof(*addr));
+
+	if (0 == re_regex(pl->p, pl->l, "[~ \t\r\n<]*[ \t\r\n]*<[^>]+>[^]*",
+			  &addr->dname, NULL, &addr->auri, &addr->params)) {
+
+		if (!addr->dname.l)
+			addr->dname.p = NULL;
+
+		if (!addr->params.l)
+			addr->params.p = NULL;
+	}
+	else {
+		memset(addr, 0, sizeof(*addr));
+
+		if (re_regex(pl->p, pl->l, "[^;]+[^]*",
+			     &addr->auri, &addr->params))
+			return EBADMSG;
+	}
+
+	err = uri_decode(&addr->uri, &addr->auri);
+	if (err)
+		memset(addr, 0, sizeof(*addr));
+
+	return err;
+}
diff --git a/src/sip/auth.c b/src/sip/auth.c
new file mode 100644
index 0000000..1357cad
--- /dev/null
+++ b/src/sip/auth.c
@@ -0,0 +1,325 @@
+/**
+ * @file sip/auth.c  SIP Authentication
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_uri.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_sys.h>
+#include <re_md5.h>
+#include <re_httpauth.h>
+#include <re_udp.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+struct sip_auth {
+	struct list realml;
+	sip_auth_h *authh;
+	void *arg;
+	bool ref;
+	int err;
+};
+
+
+struct realm {
+	struct le le;
+	char *realm;
+	char *nonce;
+	char *qop;
+	char *opaque;
+	char *user;
+	char *pass;
+	uint32_t nc;
+	enum sip_hdrid hdr;
+};
+
+
+static int dummy_handler(char **user, char **pass, const char *rlm, void *arg)
+{
+	(void)user;
+	(void)pass;
+	(void)rlm;
+	(void)arg;
+
+	return EAUTH;
+}
+
+
+static void realm_destructor(void *arg)
+{
+	struct realm *realm = arg;
+
+	list_unlink(&realm->le);
+	mem_deref(realm->realm);
+	mem_deref(realm->nonce);
+	mem_deref(realm->qop);
+	mem_deref(realm->opaque);
+	mem_deref(realm->user);
+	mem_deref(realm->pass);
+}
+
+
+static void auth_destructor(void *arg)
+{
+	struct sip_auth *auth = arg;
+
+	if (auth->ref)
+		mem_deref(auth->arg);
+
+	list_flush(&auth->realml);
+}
+
+
+static int mkdigest(uint8_t *digest, const struct realm *realm,
+		    const char *met, const char *uri, uint64_t cnonce)
+{
+	uint8_t ha1[MD5_SIZE], ha2[MD5_SIZE];
+	int err;
+
+	err = md5_printf(ha1, "%s:%s:%s",
+			 realm->user, realm->realm, realm->pass);
+	if (err)
+		return err;
+
+	err = md5_printf(ha2, "%s:%s", met, uri);
+	if (err)
+		return err;
+
+	if (realm->qop)
+		return md5_printf(digest, "%w:%s:%08x:%016llx:auth:%w",
+				  ha1, sizeof(ha1),
+				  realm->nonce,
+				  realm->nc,
+				  cnonce,
+				  ha2, sizeof(ha2));
+	else
+		return md5_printf(digest, "%w:%s:%w",
+				  ha1, sizeof(ha1),
+				  realm->nonce,
+				  ha2, sizeof(ha2));
+}
+
+
+static bool cmp_handler(struct le *le, void *arg)
+{
+	struct realm *realm = le->data;
+	struct pl *chrealm = arg;
+
+	/* handle multiple authenticate headers with equal realm value */
+	if (realm->nc == 1)
+		return false;
+
+	return 0 == pl_strcasecmp(chrealm, realm->realm);
+}
+
+
+static bool auth_handler(const struct sip_hdr *hdr, const struct sip_msg *msg,
+			 void *arg)
+{
+	struct httpauth_digest_chall ch;
+	struct sip_auth *auth = arg;
+	struct realm *realm = NULL;
+	int err;
+	(void)msg;
+
+	if (httpauth_digest_challenge_decode(&ch, &hdr->val)) {
+		err = EBADMSG;
+		goto out;
+	}
+
+	if (pl_isset(&ch.algorithm) && pl_strcasecmp(&ch.algorithm, "md5")) {
+		err = ENOSYS;
+		goto out;
+	}
+
+	realm = list_ledata(list_apply(&auth->realml, true, cmp_handler,
+				       &ch.realm));
+	if (!realm) {
+		realm = mem_zalloc(sizeof(*realm), realm_destructor);
+		if (!realm) {
+			err = ENOMEM;
+			goto out;
+		}
+
+		list_append(&auth->realml, &realm->le, realm);
+
+		err = pl_strdup(&realm->realm, &ch.realm);
+		if (err)
+			goto out;
+
+		err = auth->authh(&realm->user, &realm->pass,
+				  realm->realm, auth->arg);
+		if (err)
+			goto out;
+	}
+	else {
+		if (!pl_isset(&ch.stale) || pl_strcasecmp(&ch.stale, "true")) {
+			err = EAUTH;
+			goto out;
+		}
+
+		realm->nonce  = mem_deref(realm->nonce);
+		realm->qop    = mem_deref(realm->qop);
+		realm->opaque = mem_deref(realm->opaque);
+	}
+
+	realm->hdr = hdr->id;
+	realm->nc  = 1;
+
+	err = pl_strdup(&realm->nonce, &ch.nonce);
+
+	if (pl_isset(&ch.qop))
+		err |= pl_strdup(&realm->qop, &ch.qop);
+
+	if (pl_isset(&ch.opaque))
+		err |= pl_strdup(&realm->opaque, &ch.opaque);
+
+ out:
+	if (err) {
+		mem_deref(realm);
+		auth->err = err;
+		return true;
+	}
+
+	return false;
+}
+
+
+/**
+ * Update a SIP authentication state from a SIP message
+ *
+ * @param auth SIP Authentication state
+ * @param msg  SIP Message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_auth_authenticate(struct sip_auth *auth, const struct sip_msg *msg)
+{
+	if (!auth || !msg)
+		return EINVAL;
+
+	if (sip_msg_hdr_apply(msg, true, SIP_HDR_WWW_AUTHENTICATE,
+			      auth_handler, auth))
+		return auth->err;
+
+	if (sip_msg_hdr_apply(msg, true, SIP_HDR_PROXY_AUTHENTICATE,
+			      auth_handler, auth))
+		return auth->err;
+
+	return 0;
+}
+
+
+int sip_auth_encode(struct mbuf *mb, struct sip_auth *auth, const char *met,
+		    const char *uri)
+{
+	struct le *le;
+	int err = 0;
+
+	if (!mb || !auth || !met || !uri)
+		return EINVAL;
+
+	for (le = auth->realml.head; le; le = le->next) {
+
+		const uint64_t cnonce = rand_u64();
+		struct realm *realm = le->data;
+		uint8_t digest[MD5_SIZE];
+
+		err = mkdigest(digest, realm, met, uri, cnonce);
+		if (err)
+			break;
+
+		switch (realm->hdr) {
+
+		case SIP_HDR_WWW_AUTHENTICATE:
+			err = mbuf_write_str(mb, "Authorization: ");
+			break;
+
+		case SIP_HDR_PROXY_AUTHENTICATE:
+			err = mbuf_write_str(mb, "Proxy-Authorization: ");
+			break;
+
+		default:
+			continue;
+		}
+
+		err |= mbuf_printf(mb, "Digest username=\"%s\"", realm->user);
+		err |= mbuf_printf(mb, ", realm=\"%s\"", realm->realm);
+		err |= mbuf_printf(mb, ", nonce=\"%s\"", realm->nonce);
+		err |= mbuf_printf(mb, ", uri=\"%s\"", uri);
+		err |= mbuf_printf(mb, ", response=\"%w\"",
+				   digest, sizeof(digest));
+
+		if (realm->opaque)
+			err |= mbuf_printf(mb, ", opaque=\"%s\"",
+					   realm->opaque);
+
+		if (realm->qop) {
+			err |= mbuf_printf(mb, ", cnonce=\"%016llx\"", cnonce);
+			err |= mbuf_write_str(mb, ", qop=auth");
+			err |= mbuf_printf(mb, ", nc=%08x", realm->nc);
+		}
+
+		++realm->nc;
+
+		err |= mbuf_write_str(mb, "\r\n");
+		if (err)
+			break;
+	}
+
+	return err;
+}
+
+
+/**
+ * Allocate a SIP authentication state
+ *
+ * @param authp Pointer to allocated SIP authentication state
+ * @param authh Authentication handler
+ * @param arg   Handler argument
+ * @param ref   True to mem_ref() argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_auth_alloc(struct sip_auth **authp, sip_auth_h *authh,
+		   void *arg, bool ref)
+{
+	struct sip_auth *auth;
+
+	if (!authp)
+		return EINVAL;
+
+	auth = mem_zalloc(sizeof(*auth), auth_destructor);
+	if (!auth)
+		return ENOMEM;
+
+	auth->authh = authh ? authh : dummy_handler;
+	auth->arg   = ref ? mem_ref(arg) : arg;
+	auth->ref   = ref;
+
+	*authp = auth;
+
+	return 0;
+}
+
+
+/**
+ * Reset a SIP authentication state
+ *
+ * @param auth SIP Authentication state
+ */
+void sip_auth_reset(struct sip_auth *auth)
+{
+	if (!auth)
+		return;
+
+	list_flush(&auth->realml);
+}
diff --git a/src/sip/contact.c b/src/sip/contact.c
new file mode 100644
index 0000000..6703384
--- /dev/null
+++ b/src/sip/contact.c
@@ -0,0 +1,57 @@
+/**
+ * @file sip/contact.c  SIP contact functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_uri.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_msg.h>
+#include <re_sip.h>
+
+
+/**
+ * Set contact parameters
+ *
+ * @param contact SIP Contact object
+ * @param uri     Username or URI
+ * @param addr    IP-address and port
+ * @param tp      SIP Transport
+ */
+void sip_contact_set(struct sip_contact *contact, const char *uri,
+		     const struct sa *addr, enum sip_transp tp)
+{
+	if (!contact)
+		return;
+
+	contact->uri  = uri;
+	contact->addr = addr;
+	contact->tp   = tp;
+}
+
+
+/**
+ * Print contact header
+ *
+ * @param pf      Print function
+ * @param contact SIP Contact object
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int sip_contact_print(struct re_printf *pf, const struct sip_contact *contact)
+{
+	if (!contact)
+		return 0;
+
+	if (contact->uri && strchr(contact->uri, ':'))
+		return re_hprintf(pf, "Contact: <%s>\r\n", contact->uri);
+	else
+		return re_hprintf(pf, "Contact: <sip:%s@%J%s>\r\n",
+				  contact->uri,
+				  contact->addr,
+				  sip_transp_param(contact->tp));
+}
diff --git a/src/sip/cseq.c b/src/sip/cseq.c
new file mode 100644
index 0000000..1be247b
--- /dev/null
+++ b/src/sip/cseq.c
@@ -0,0 +1,40 @@
+/**
+ * @file cseq.c  SIP CSeq decode
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_uri.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_msg.h>
+#include <re_sip.h>
+
+
+/**
+ * Decode a pointer-length string into a SIP CSeq header
+ *
+ * @param cseq SIP CSeq header
+ * @param pl   Pointer-length string
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int sip_cseq_decode(struct sip_cseq *cseq, const struct pl *pl)
+{
+	struct pl num;
+	int err;
+
+	if (!cseq || !pl)
+		return EINVAL;
+
+	err = re_regex(pl->p, pl->l, "[0-9]+[ \t\r\n]+[^ \t\r\n]+",
+		       &num, NULL, &cseq->met);
+	if (err)
+		return err;
+
+	cseq->num = pl_u32(&num);
+
+	return 0;
+}
diff --git a/src/sip/ctrans.c b/src/sip/ctrans.c
new file mode 100644
index 0000000..0ff3266
--- /dev/null
+++ b/src/sip/ctrans.c
@@ -0,0 +1,449 @@
+/**
+ * @file sip/ctrans.c  SIP Client Transaction
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_sys.h>
+#include <re_tmr.h>
+#include <re_udp.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+enum state {
+	TRYING,
+	CALLING,
+	PROCEEDING,
+	COMPLETED,
+};
+
+
+enum {
+	COMPLETE_WAIT = 32000,
+};
+
+
+struct sip_ctrans {
+	struct le he;
+	struct sa dst;
+	struct tmr tmr;
+	struct tmr tmre;
+	struct sip *sip;
+	struct mbuf *mb;
+	struct mbuf *mb_ack;
+	struct sip_msg *req;
+	struct sip_connqent *qent;
+	char *met;
+	char *branch;
+	sip_resp_h *resph;
+	void *arg;
+	enum sip_transp tp;
+	enum state state;
+	uint32_t txc;
+	bool invite;
+};
+
+
+static bool route_handler(const struct sip_hdr *hdr, const struct sip_msg *msg,
+			  void *arg)
+{
+	(void)msg;
+	return 0 != mbuf_printf(arg, "Route: %r\r\n", &hdr->val);
+}
+
+
+static int request_copy(struct mbuf **mbp, struct sip_ctrans *ct,
+			const char *met, const struct sip_msg *resp)
+{
+	struct mbuf *mb;
+	int err;
+
+	if (!ct->req) {
+		err = sip_msg_decode(&ct->req, ct->mb);
+		if (err)
+			return err;
+	}
+
+	mb = mbuf_alloc(1024);
+	if (!mb)
+		return ENOMEM;
+
+	err  = mbuf_printf(mb, "%s %r SIP/2.0\r\n", met, &ct->req->ruri);
+	err |= mbuf_printf(mb, "Via: %r\r\n", &ct->req->via.val);
+	err |= mbuf_write_str(mb, "Max-Forwards: 70\r\n");
+	err |= sip_msg_hdr_apply(ct->req, true, SIP_HDR_ROUTE,
+				 route_handler, mb) ? ENOMEM : 0;
+	err |= mbuf_printf(mb, "To: %r\r\n",
+			   resp ? &resp->to.val : &ct->req->to.val);
+	err |= mbuf_printf(mb, "From: %r\r\n", &ct->req->from.val);
+	err |= mbuf_printf(mb, "Call-ID: %r\r\n", &ct->req->callid);
+	err |= mbuf_printf(mb, "CSeq: %u %s\r\n", ct->req->cseq.num, met);
+	if (ct->sip->software)
+		err |= mbuf_printf(mb, "User-Agent: %s\r\n",ct->sip->software);
+	err |= mbuf_write_str(mb, "Content-Length: 0\r\n\r\n");
+
+	mb->pos = 0;
+
+	if (err)
+		mem_deref(mb);
+	else
+		*mbp = mb;
+
+	return err;
+}
+
+
+static void destructor(void *arg)
+{
+	struct sip_ctrans *ct = arg;
+
+	hash_unlink(&ct->he);
+	tmr_cancel(&ct->tmr);
+	tmr_cancel(&ct->tmre);
+	mem_deref(ct->met);
+	mem_deref(ct->branch);
+	mem_deref(ct->qent);
+	mem_deref(ct->req);
+	mem_deref(ct->mb);
+	mem_deref(ct->mb_ack);
+}
+
+
+static bool cmp_handler(struct le *le, void *arg)
+{
+	struct sip_ctrans *ct = le->data;
+	const struct sip_msg *msg = arg;
+
+	if (pl_strcmp(&msg->via.branch, ct->branch))
+		return false;
+
+	if (pl_strcmp(&msg->cseq.met, ct->met))
+		return false;
+
+	return true;
+}
+
+
+static void dummy_handler(int err, const struct sip_msg *msg, void *arg)
+{
+	(void)err;
+	(void)msg;
+	(void)arg;
+}
+
+
+static void terminate(struct sip_ctrans *ct, int err)
+{
+	switch (ct->state) {
+
+	case TRYING:
+	case CALLING:
+	case PROCEEDING:
+		ct->resph(err, NULL, ct->arg);
+		break;
+
+	default:
+		break;
+	}
+}
+
+
+static void transport_handler(int err, void *arg)
+{
+	struct sip_ctrans *ct = arg;
+
+	terminate(ct, err);
+	mem_deref(ct);
+}
+
+
+static void tmr_handler(void *arg)
+{
+	struct sip_ctrans *ct = arg;
+
+	terminate(ct, ETIMEDOUT);
+	mem_deref(ct);
+}
+
+
+static void retransmit_handler(void *arg)
+{
+	struct sip_ctrans *ct = arg;
+	uint32_t timeout;
+	int err;
+
+	ct->txc++;
+
+	switch (ct->state) {
+
+	case TRYING:
+		timeout = MIN(SIP_T1<<ct->txc, SIP_T2);
+		break;
+
+	case CALLING:
+		timeout = SIP_T1<<ct->txc;
+		break;
+
+	case PROCEEDING:
+		timeout = SIP_T2;
+		break;
+
+	default:
+		return;
+	}
+
+	tmr_start(&ct->tmre, timeout, retransmit_handler, ct);
+
+	err = sip_transp_send(&ct->qent, ct->sip, NULL, ct->tp, &ct->dst,
+			      ct->mb, transport_handler, ct);
+	if (err) {
+		terminate(ct, err);
+		mem_deref(ct);
+	}
+}
+
+
+static void invite_response(struct sip_ctrans *ct, const struct sip_msg *msg)
+{
+	switch (ct->state) {
+
+	case CALLING:
+		tmr_cancel(&ct->tmr);
+		tmr_cancel(&ct->tmre);
+		/*@fallthrough@*/
+	case PROCEEDING:
+		if (msg->scode < 200) {
+			ct->state = PROCEEDING;
+			ct->resph(0, msg, ct->arg);
+		}
+		else if (msg->scode < 300) {
+			ct->resph(0, msg, ct->arg);
+			mem_deref(ct);
+		}
+		else {
+			ct->state = COMPLETED;
+
+			(void)request_copy(&ct->mb_ack, ct, "ACK", msg);
+			(void)sip_send(ct->sip, NULL, ct->tp, &ct->dst,
+				       ct->mb_ack);
+
+			ct->resph(0, msg, ct->arg);
+
+			if (sip_transp_reliable(ct->tp)) {
+				mem_deref(ct);
+				break;
+			}
+
+			tmr_start(&ct->tmr, COMPLETE_WAIT, tmr_handler, ct);
+		}
+		break;
+
+	case COMPLETED:
+		if (msg->scode < 300)
+			break;
+
+		(void)sip_send(ct->sip, NULL, ct->tp, &ct->dst, ct->mb_ack);
+		break;
+
+	default:
+		break;
+	}
+}
+
+
+static bool response_handler(const struct sip_msg *msg, void *arg)
+{
+	struct sip_ctrans *ct;
+	struct sip *sip = arg;
+
+	ct = list_ledata(hash_lookup(sip->ht_ctrans,
+				     hash_joaat_pl(&msg->via.branch),
+				     cmp_handler, (void *)msg));
+	if (!ct)
+		return false;
+
+	if (ct->invite) {
+		invite_response(ct, msg);
+		return true;
+	}
+
+	switch (ct->state) {
+
+	case TRYING:
+	case PROCEEDING:
+		if (msg->scode < 200) {
+			ct->state = PROCEEDING;
+			ct->resph(0, msg, ct->arg);
+		}
+		else {
+			ct->state = COMPLETED;
+			ct->resph(0, msg, ct->arg);
+
+			if (sip_transp_reliable(ct->tp)) {
+				mem_deref(ct);
+				break;
+			}
+
+			tmr_start(&ct->tmr, SIP_T4, tmr_handler, ct);
+			tmr_cancel(&ct->tmre);
+		}
+		break;
+
+	default:
+		break;
+	}
+
+	return true;
+}
+
+
+int sip_ctrans_request(struct sip_ctrans **ctp, struct sip *sip,
+		       enum sip_transp tp, const struct sa *dst, char *met,
+		       char *branch, struct mbuf *mb,
+		       sip_resp_h *resph, void *arg)
+{
+	struct sip_ctrans *ct;
+	int err;
+
+	if (!sip || !dst || !met || !branch || !mb)
+		return EINVAL;
+
+	ct = mem_zalloc(sizeof(*ct), destructor);
+	if (!ct)
+		return ENOMEM;
+
+	hash_append(sip->ht_ctrans, hash_joaat_str(branch), &ct->he, ct);
+
+	ct->invite = !strcmp(met, "INVITE");
+	ct->branch = mem_ref(branch);
+	ct->met    = mem_ref(met);
+	ct->mb     = mem_ref(mb);
+	ct->dst    = *dst;
+	ct->tp     = tp;
+	ct->sip    = sip;
+	ct->state  = ct->invite ? CALLING : TRYING;
+	ct->resph  = resph ? resph : dummy_handler;
+	ct->arg    = arg;
+
+	err = sip_transp_send(&ct->qent, sip, NULL, tp, dst, mb,
+			      transport_handler, ct);
+	if (err)
+		goto out;
+
+	tmr_start(&ct->tmr, 64 * SIP_T1, tmr_handler, ct);
+
+	if (!sip_transp_reliable(ct->tp))
+		tmr_start(&ct->tmre, SIP_T1, retransmit_handler, ct);
+
+ out:
+	if (err)
+		mem_deref(ct);
+	else if (ctp)
+		*ctp = ct;
+
+	return err;
+}
+
+
+int sip_ctrans_cancel(struct sip_ctrans *ct)
+{
+	struct mbuf *mb = NULL;
+	char *cancel = NULL;
+	int err;
+
+	if (!ct)
+		return EINVAL;
+
+	if (!ct->invite)
+		return 0;
+
+	switch (ct->state) {
+
+	case PROCEEDING:
+		tmr_start(&ct->tmr, 64 * SIP_T1, tmr_handler, ct);
+		break;
+
+	default:
+		return EPROTO;
+	}
+
+	err = str_dup(&cancel, "CANCEL");
+	if (err)
+		goto out;
+
+	err = request_copy(&mb, ct, cancel, NULL);
+	if (err)
+		goto out;
+
+	err = sip_ctrans_request(NULL, ct->sip, ct->tp, &ct->dst, cancel,
+				 ct->branch, mb, NULL, NULL);
+	if (err)
+		goto out;
+
+ out:
+	mem_deref(cancel);
+	mem_deref(mb);
+
+	return err;
+}
+
+
+int sip_ctrans_init(struct sip *sip, uint32_t sz)
+{
+	int err;
+
+	err = sip_listen(NULL, sip, false, response_handler, sip);
+	if (err)
+		return err;
+
+	return hash_alloc(&sip->ht_ctrans, sz);
+}
+
+
+static const char *statename(enum state state)
+{
+	switch (state) {
+
+	case TRYING:     return "TRYING";
+	case CALLING:    return "CALLING";
+	case PROCEEDING: return "PROCEEDING";
+	case COMPLETED:  return "COMPLETED";
+	default:         return "???";
+	}
+}
+
+
+static bool debug_handler(struct le *le, void *arg)
+{
+	struct sip_ctrans *ct = le->data;
+	struct re_printf *pf = arg;
+
+	(void)re_hprintf(pf, "  %-10s %-10s %2llus (%s)\n",
+			 ct->met,
+			 statename(ct->state),
+			 tmr_get_expire(&ct->tmr)/1000,
+			 ct->branch);
+
+	return false;
+}
+
+
+int sip_ctrans_debug(struct re_printf *pf, const struct sip *sip)
+{
+	int err;
+
+	err = re_hprintf(pf, "client transactions:\n");
+	hash_apply(sip->ht_ctrans, debug_handler, pf);
+
+	return err;
+}
diff --git a/src/sip/dialog.c b/src/sip/dialog.c
new file mode 100644
index 0000000..5863e20
--- /dev/null
+++ b/src/sip/dialog.c
@@ -0,0 +1,659 @@
+/**
+ * @file dialog.c  SIP Dialog
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_sys.h>
+#include <re_tmr.h>
+#include <re_udp.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+enum {
+	ROUTE_OFFSET = 7,
+	X64_STRSIZE = 17,
+};
+
+struct sip_dialog {
+	struct uri route;
+	struct mbuf *mb;
+	char *callid;
+	char *ltag;
+	char *rtag;
+	char *uri;
+	uint32_t hash;
+	uint32_t lseq;
+	uint32_t rseq;
+	size_t cpos;
+};
+
+
+struct route_enc {
+	struct mbuf *mb;
+	size_t end;
+};
+
+
+static int x64_strdup(char **strp, uint64_t val)
+{
+	char *str;
+
+	str = mem_alloc(X64_STRSIZE, NULL);
+	if (!str)
+		return ENOMEM;
+
+	(void)re_snprintf(str, X64_STRSIZE, "%016llx", val);
+
+	*strp = str;
+
+	return 0;
+}
+
+
+static void destructor(void *arg)
+{
+	struct sip_dialog *dlg = arg;
+
+	mem_deref(dlg->callid);
+	mem_deref(dlg->ltag);
+	mem_deref(dlg->rtag);
+	mem_deref(dlg->uri);
+	mem_deref(dlg->mb);
+}
+
+
+/**
+ * Allocate a SIP Dialog
+ *
+ * @param dlgp      Pointer to allocated SIP Dialog
+ * @param uri       Target URI
+ * @param to_uri    To URI
+ * @param from_name From displayname (optional)
+ * @param from_uri  From URI
+ * @param routev    Route vector
+ * @param routec    Route count
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_dialog_alloc(struct sip_dialog **dlgp,
+		     const char *uri, const char *to_uri,
+		     const char *from_name, const char *from_uri,
+		     const char *routev[], uint32_t routec)
+{
+	const uint64_t ltag = rand_u64();
+	struct sip_dialog *dlg;
+	struct sip_addr addr;
+	size_t rend = 0;
+	struct pl pl;
+	uint32_t i;
+	int err;
+
+	if (!dlgp || !uri || !to_uri || !from_uri)
+		return EINVAL;
+
+	dlg = mem_zalloc(sizeof(*dlg), destructor);
+	if (!dlg)
+		return ENOMEM;
+
+	dlg->hash = hash_fast_str(from_uri);
+	dlg->lseq = rand_u16();
+
+	err = str_dup(&dlg->uri, uri);
+	if (err)
+		goto out;
+
+	err = x64_strdup(&dlg->callid, rand_u64());
+	if (err)
+		goto out;
+
+	err = x64_strdup(&dlg->ltag, ltag);
+	if (err)
+		goto out;
+
+	dlg->mb = mbuf_alloc(512);
+	if (!dlg->mb) {
+		err = ENOMEM;
+		goto out;
+	}
+
+	for (i=0; i<routec; i++) {
+		err |= mbuf_printf(dlg->mb, "Route: <%s;lr>\r\n", routev[i]);
+		if (i == 0)
+			rend = dlg->mb->pos - 2;
+	}
+	err |= mbuf_printf(dlg->mb, "To: <%s>\r\n", to_uri);
+	dlg->cpos = dlg->mb->pos;
+	err |= mbuf_printf(dlg->mb, "From: %s%s%s<%s>;tag=%016llx\r\n",
+			   from_name ? "\"" : "", from_name,
+			   from_name ? "\" " : "",
+			   from_uri, ltag);
+	if (err)
+		goto out;
+
+	dlg->mb->pos = 0;
+
+	if (rend) {
+		pl.p = (const char *)mbuf_buf(dlg->mb) + ROUTE_OFFSET;
+		pl.l = rend - ROUTE_OFFSET;
+		err = sip_addr_decode(&addr, &pl);
+		dlg->route = addr.uri;
+	}
+	else {
+		pl_set_str(&pl, dlg->uri);
+		err = uri_decode(&dlg->route, &pl);
+	}
+
+ out:
+	if (err)
+		mem_deref(dlg);
+	else
+		*dlgp = dlg;
+
+	return err;
+}
+
+
+static bool record_route_handler(const struct sip_hdr *hdr,
+				 const struct sip_msg *msg,
+				 void *arg)
+{
+	struct route_enc *renc = arg;
+	(void)msg;
+
+	if (mbuf_printf(renc->mb, "Route: %r\r\n", &hdr->val))
+		return true;
+
+	if (!renc->end)
+	        renc->end = renc->mb->pos - 2;
+
+	return false;
+}
+
+
+/**
+ * Accept and create a SIP Dialog from an incoming SIP Message
+ *
+ * @param dlgp Pointer to allocated SIP Dialog
+ * @param msg  SIP Message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_dialog_accept(struct sip_dialog **dlgp, const struct sip_msg *msg)
+{
+	const struct sip_hdr *contact;
+	struct sip_dialog *dlg;
+	struct route_enc renc;
+	struct sip_addr addr;
+	struct pl pl;
+	int err;
+
+	if (!dlgp || !msg || !msg->req)
+		return EINVAL;
+
+	contact = sip_msg_hdr(msg, SIP_HDR_CONTACT);
+
+	if (!contact || !msg->callid.p)
+		return EBADMSG;
+
+	if (sip_addr_decode(&addr, &contact->val))
+		return EBADMSG;
+
+	dlg = mem_zalloc(sizeof(*dlg), destructor);
+	if (!dlg)
+		return ENOMEM;
+
+	dlg->hash = rand_u32();
+	dlg->lseq = rand_u16();
+	dlg->rseq = msg->cseq.num;
+
+	err = pl_strdup(&dlg->uri, &addr.auri);
+	if (err)
+		goto out;
+
+	err = pl_strdup(&dlg->callid, &msg->callid);
+	if (err)
+		goto out;
+
+	err = x64_strdup(&dlg->ltag, msg->tag);
+	if (err)
+		goto out;
+
+	err = pl_strdup(&dlg->rtag, &msg->from.tag);
+	if (err)
+		goto out;
+
+	dlg->mb = mbuf_alloc(512);
+	if (!dlg->mb) {
+		err = ENOMEM;
+		goto out;
+	}
+
+	renc.mb  = dlg->mb;
+	renc.end = 0;
+
+	err |= sip_msg_hdr_apply(msg, true, SIP_HDR_RECORD_ROUTE,
+				 record_route_handler, &renc) ? ENOMEM : 0;
+	err |= mbuf_printf(dlg->mb, "To: %r\r\n", &msg->from.val);
+	err |= mbuf_printf(dlg->mb, "From: %r;tag=%016llx\r\n", &msg->to.val,
+			   msg->tag);
+	if (err)
+		goto out;
+
+	dlg->mb->pos = 0;
+
+	if (renc.end) {
+		pl.p = (const char *)mbuf_buf(dlg->mb) + ROUTE_OFFSET;
+		pl.l = renc.end - ROUTE_OFFSET;
+		err = sip_addr_decode(&addr, &pl);
+		dlg->route = addr.uri;
+	}
+	else {
+		pl_set_str(&pl, dlg->uri);
+		err = uri_decode(&dlg->route, &pl);
+	}
+
+ out:
+	if (err)
+		mem_deref(dlg);
+	else
+		*dlgp = dlg;
+
+	return err;
+}
+
+
+/**
+ * Initialize a SIP Dialog from an incoming SIP Message
+ *
+ * @param dlg SIP Dialog to initialize
+ * @param msg SIP Message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_dialog_create(struct sip_dialog *dlg, const struct sip_msg *msg)
+{
+	char *uri = NULL, *rtag = NULL;
+	const struct sip_hdr *contact;
+	struct route_enc renc;
+	struct sip_addr addr;
+	struct pl pl;
+	int err;
+
+	if (!dlg || dlg->rtag || !dlg->cpos || !msg)
+		return EINVAL;
+
+	contact = sip_msg_hdr(msg, SIP_HDR_CONTACT);
+
+	if (!contact)
+		return EBADMSG;
+
+	if (sip_addr_decode(&addr, &contact->val))
+		return EBADMSG;
+
+	renc.mb = mbuf_alloc(512);
+	if (!renc.mb)
+		return ENOMEM;
+
+	err = pl_strdup(&uri, &addr.auri);
+	if (err)
+		goto out;
+
+	err = pl_strdup(&rtag, msg->req ? &msg->from.tag : &msg->to.tag);
+	if (err)
+		goto out;
+
+	renc.end = 0;
+
+	err |= sip_msg_hdr_apply(msg, msg->req, SIP_HDR_RECORD_ROUTE,
+				 record_route_handler, &renc) ? ENOMEM : 0;
+	err |= mbuf_printf(renc.mb, "To: %r\r\n",
+			   msg->req ? &msg->from.val : &msg->to.val);
+
+	dlg->mb->pos = dlg->cpos;
+	err |= mbuf_write_mem(renc.mb, mbuf_buf(dlg->mb),
+			      mbuf_get_left(dlg->mb));
+	dlg->mb->pos = 0;
+
+	if (err)
+		goto out;
+
+	renc.mb->pos = 0;
+
+	if (renc.end) {
+		pl.p = (const char *)mbuf_buf(renc.mb) + ROUTE_OFFSET;
+		pl.l = renc.end - ROUTE_OFFSET;
+		err = sip_addr_decode(&addr, &pl);
+		if (err)
+			goto out;
+
+		dlg->route = addr.uri;
+	}
+	else {
+		struct uri tmp;
+
+		pl_set_str(&pl, uri);
+		err = uri_decode(&tmp, &pl);
+		if (err)
+			goto out;
+
+		dlg->route = tmp;
+	}
+
+	mem_deref(dlg->mb);
+	mem_deref(dlg->uri);
+
+	dlg->mb   = mem_ref(renc.mb);
+	dlg->rtag = mem_ref(rtag);
+	dlg->uri  = mem_ref(uri);
+	dlg->rseq = msg->req ? msg->cseq.num : 0;
+	dlg->cpos = 0;
+
+ out:
+	mem_deref(renc.mb);
+	mem_deref(rtag);
+	mem_deref(uri);
+
+	return err;
+}
+
+
+/**
+ * Fork a SIP Dialog from an incoming SIP Message
+ *
+ * @param dlgp Pointer to allocated SIP Dialog
+ * @param odlg Original SIP Dialog
+ * @param msg  SIP Message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_dialog_fork(struct sip_dialog **dlgp, struct sip_dialog *odlg,
+		    const struct sip_msg *msg)
+{
+	const struct sip_hdr *contact;
+	struct sip_dialog *dlg;
+	struct route_enc renc;
+	struct sip_addr addr;
+	struct pl pl;
+	int err;
+
+	if (!dlgp || !odlg || !odlg->cpos || !msg)
+		return EINVAL;
+
+	contact = sip_msg_hdr(msg, SIP_HDR_CONTACT);
+
+	if (!contact || !msg->callid.p)
+		return EBADMSG;
+
+	if (sip_addr_decode(&addr, &contact->val))
+		return EBADMSG;
+
+	dlg = mem_zalloc(sizeof(*dlg), destructor);
+	if (!dlg)
+		return ENOMEM;
+
+	dlg->callid = mem_ref(odlg->callid);
+	dlg->ltag   = mem_ref(odlg->ltag);
+	dlg->hash   = odlg->hash;
+	dlg->lseq   = odlg->lseq;
+	dlg->rseq   = msg->req ? msg->cseq.num : 0;
+
+	err = pl_strdup(&dlg->uri, &addr.auri);
+	if (err)
+		goto out;
+
+	err = pl_strdup(&dlg->rtag, msg->req ? &msg->from.tag : &msg->to.tag);
+	if (err)
+		goto out;
+
+	dlg->mb = mbuf_alloc(512);
+	if (!dlg->mb) {
+		err = ENOMEM;
+		goto out;
+	}
+
+	renc.mb  = dlg->mb;
+	renc.end = 0;
+
+	err |= sip_msg_hdr_apply(msg, msg->req, SIP_HDR_RECORD_ROUTE,
+				 record_route_handler, &renc) ? ENOMEM : 0;
+	err |= mbuf_printf(dlg->mb, "To: %r\r\n",
+			   msg->req ? &msg->from.val : &msg->to.val);
+
+	odlg->mb->pos = odlg->cpos;
+	err |= mbuf_write_mem(dlg->mb, mbuf_buf(odlg->mb),
+			      mbuf_get_left(odlg->mb));
+	odlg->mb->pos = 0;
+
+	if (err)
+		goto out;
+
+	dlg->mb->pos = 0;
+
+	if (renc.end) {
+		pl.p = (const char *)mbuf_buf(dlg->mb) + ROUTE_OFFSET;
+		pl.l = renc.end - ROUTE_OFFSET;
+		err = sip_addr_decode(&addr, &pl);
+		dlg->route = addr.uri;
+	}
+	else {
+		pl_set_str(&pl, dlg->uri);
+		err = uri_decode(&dlg->route, &pl);
+	}
+
+ out:
+	if (err)
+		mem_deref(dlg);
+	else
+		*dlgp = dlg;
+
+	return err;
+}
+
+
+/**
+ * Update an existing SIP Dialog from a SIP Message
+ *
+ * @param dlg SIP Dialog to update
+ * @param msg SIP Message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_dialog_update(struct sip_dialog *dlg, const struct sip_msg *msg)
+{
+	const struct sip_hdr *contact;
+	struct sip_addr addr;
+	char *uri;
+	int err;
+
+	if (!dlg || !msg)
+		return EINVAL;
+
+	contact = sip_msg_hdr(msg, SIP_HDR_CONTACT);
+	if (!contact)
+		return EBADMSG;
+
+	if (sip_addr_decode(&addr, &contact->val))
+		return EBADMSG;
+
+	err = pl_strdup(&uri, &addr.auri);
+	if (err)
+		return err;
+
+	if (dlg->route.scheme.p == dlg->uri) {
+
+		struct uri tmp;
+		struct pl pl;
+
+		pl_set_str(&pl, uri);
+		err = uri_decode(&tmp, &pl);
+		if (err)
+			goto out;
+
+		dlg->route = tmp;
+	}
+
+	mem_deref(dlg->uri);
+	dlg->uri = mem_ref(uri);
+
+ out:
+	mem_deref(uri);
+
+	return err;
+}
+
+
+/**
+ * Check if a remote sequence number is valid
+ *
+ * @param dlg SIP Dialog
+ * @param msg SIP Message
+ *
+ * @return True if valid, False if invalid
+ */
+bool sip_dialog_rseq_valid(struct sip_dialog *dlg, const struct sip_msg *msg)
+{
+	if (!dlg || !msg || !msg->req)
+		return false;
+
+	if (msg->cseq.num < dlg->rseq)
+		return false;
+
+	dlg->rseq = msg->cseq.num;
+
+	return true;
+}
+
+
+int sip_dialog_encode(struct mbuf *mb, struct sip_dialog *dlg, uint32_t cseq,
+		      const char *met)
+{
+	int err = 0;
+
+	if (!mb || !dlg || !met)
+		return EINVAL;
+
+	err |= mbuf_write_mem(mb, mbuf_buf(dlg->mb), mbuf_get_left(dlg->mb));
+	err |= mbuf_printf(mb, "Call-ID: %s\r\n", dlg->callid);
+	err |= mbuf_printf(mb, "CSeq: %u %s\r\n", strcmp(met, "ACK") ?
+			   dlg->lseq++ : cseq, met);
+
+	return err;
+}
+
+
+const char *sip_dialog_uri(const struct sip_dialog *dlg)
+{
+	return dlg ? dlg->uri : NULL;
+}
+
+
+const struct uri *sip_dialog_route(const struct sip_dialog *dlg)
+{
+	return dlg ? &dlg->route : NULL;
+}
+
+
+uint32_t sip_dialog_hash(const struct sip_dialog *dlg)
+{
+	return dlg ? dlg->hash : 0;
+}
+
+
+/**
+ * Get the Call-ID from a SIP Dialog
+ *
+ * @param dlg SIP Dialog
+ *
+ * @return Call-ID string
+ */
+const char *sip_dialog_callid(const struct sip_dialog *dlg)
+{
+	return dlg ? dlg->callid : NULL;
+}
+
+
+/**
+ * Get the local sequence number from a SIP Dialog
+ *
+ * @param dlg SIP Dialog
+ *
+ * @return Local sequence number
+ */
+uint32_t sip_dialog_lseq(const struct sip_dialog *dlg)
+{
+	return dlg ? dlg->lseq : 0;
+}
+
+
+/**
+ * Check if a SIP Dialog is established
+ *
+ * @param dlg SIP Dialog
+ *
+ * @return True if established, False if not
+ */
+bool sip_dialog_established(const struct sip_dialog *dlg)
+{
+	return dlg && dlg->rtag;
+}
+
+
+/**
+ * Compare a SIP Dialog against a SIP Message
+ *
+ * @param dlg SIP Dialog
+ * @param msg SIP Message
+ *
+ * @return True if match, False if no match
+ */
+bool sip_dialog_cmp(const struct sip_dialog *dlg, const struct sip_msg *msg)
+{
+	if (!dlg || !msg)
+		return false;
+
+	if (pl_strcmp(&msg->callid, dlg->callid))
+		return false;
+
+	if (pl_strcmp(msg->req ? &msg->to.tag : &msg->from.tag, dlg->ltag))
+		return false;
+
+	if (pl_strcmp(msg->req ? &msg->from.tag : &msg->to.tag, dlg->rtag))
+		return false;
+
+	return true;
+}
+
+
+/**
+ * Compare a half SIP Dialog against a SIP Message
+ *
+ * @param dlg SIP Dialog
+ * @param msg SIP Message
+ *
+ * @return True if match, False if no match
+ */
+bool sip_dialog_cmp_half(const struct sip_dialog *dlg,
+			 const struct sip_msg *msg)
+{
+	if (!dlg || !msg)
+		return false;
+
+	if (pl_strcmp(&msg->callid, dlg->callid))
+		return false;
+
+	if (pl_strcmp(msg->req ? &msg->to.tag : &msg->from.tag, dlg->ltag))
+		return false;
+
+	return true;
+}
diff --git a/src/sip/keepalive.c b/src/sip/keepalive.c
new file mode 100644
index 0000000..1e1594f
--- /dev/null
+++ b/src/sip/keepalive.c
@@ -0,0 +1,115 @@
+/**
+ * @file sip/keepalive.c  SIP Keepalive
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_sys.h>
+#include <re_uri.h>
+#include <re_udp.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+static void destructor(void *arg)
+{
+	struct sip_keepalive *ka = arg;
+
+	if (ka->kap)
+		*ka->kap = NULL;
+
+	list_unlink(&ka->le);
+}
+
+
+void sip_keepalive_signal(struct list *kal, int err)
+{
+	struct le *le = list_head(kal);
+
+	while (le) {
+
+		struct sip_keepalive *ka = le->data;
+		sip_keepalive_h *kah = ka->kah;
+		void *arg = ka->arg;
+
+		le = le->next;
+
+		list_unlink(&ka->le);
+		mem_deref(ka);
+
+		kah(err, arg);
+	}
+}
+
+
+uint64_t sip_keepalive_wait(uint32_t interval)
+{
+	return interval * (800 + rand_u16() % 201);
+}
+
+
+/**
+ * Start a keepalive handler on a SIP transport
+ *
+ * @param kap      Pointer to allocated keepalive object
+ * @param sip      SIP Stack instance
+ * @param msg      SIP Message
+ * @param interval Keepalive interval in seconds
+ * @param kah      Keepalive handler
+ * @param arg      Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_keepalive_start(struct sip_keepalive **kap, struct sip *sip,
+			const struct sip_msg *msg, uint32_t interval,
+			sip_keepalive_h *kah, void *arg)
+{
+	struct sip_keepalive *ka;
+	int err;
+
+	if (!kap || !sip || !msg || !kah)
+		return EINVAL;
+
+	ka = mem_zalloc(sizeof(*ka), destructor);
+	if (!ka)
+		return ENOMEM;
+
+	ka->kah = kah;
+	ka->arg = arg;
+
+	switch (msg->tp) {
+
+	case SIP_TRANSP_UDP:
+		err = sip_keepalive_udp(ka, sip, (struct udp_sock *)msg->sock,
+					&msg->src, interval);
+		break;
+
+	case SIP_TRANSP_TCP:
+	case SIP_TRANSP_TLS:
+		err = sip_keepalive_tcp(ka, (struct sip_conn *)msg->sock,
+					interval);
+		break;
+
+	default:
+		err = EPROTONOSUPPORT;
+		break;
+	}
+
+	if (err) {
+		mem_deref(ka);
+	}
+	else {
+		ka->kap = kap;
+		*kap = ka;
+	}
+
+	return err;
+}
diff --git a/src/sip/keepalive_udp.c b/src/sip/keepalive_udp.c
new file mode 100644
index 0000000..39efa89
--- /dev/null
+++ b/src/sip/keepalive_udp.c
@@ -0,0 +1,188 @@
+/**
+ * @file keepalive_udp.c  SIP UDP Keepalive
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <string.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_sys.h>
+#include <re_tmr.h>
+#include <re_udp.h>
+#include <re_stun.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+enum {
+	UDP_KEEPALIVE_INTVAL = 29,
+};
+
+
+struct sip_udpconn {
+	struct le he;
+	struct list kal;
+	struct tmr tmr_ka;
+	struct sa maddr;
+	struct sa paddr;
+	struct udp_sock *us;
+	struct stun_ctrans *ct;
+	struct stun *stun;
+	uint32_t ka_interval;
+};
+
+
+static void udpconn_keepalive_handler(void *arg);
+
+
+static void destructor(void *arg)
+{
+	struct sip_udpconn *uc = arg;
+
+	list_flush(&uc->kal);
+	hash_unlink(&uc->he);
+	tmr_cancel(&uc->tmr_ka);
+	mem_deref(uc->ct);
+	mem_deref(uc->us);
+	mem_deref(uc->stun);
+}
+
+
+static void udpconn_close(struct sip_udpconn *uc, int err)
+{
+	sip_keepalive_signal(&uc->kal, err);
+	hash_unlink(&uc->he);
+	tmr_cancel(&uc->tmr_ka);
+	uc->ct = mem_deref(uc->ct);
+	uc->us = mem_deref(uc->us);
+	uc->stun = mem_deref(uc->stun);
+}
+
+
+static void stun_response_handler(int err, uint16_t scode, const char *reason,
+				  const struct stun_msg *msg, void *arg)
+{
+	struct sip_udpconn *uc = arg;
+	struct stun_attr *attr;
+	(void)reason;
+
+	if (err || scode) {
+		err = err ? err : EPROTO;
+		goto out;
+	}
+
+	attr = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR);
+	if (!attr) {
+		attr = stun_msg_attr(msg, STUN_ATTR_MAPPED_ADDR);
+		if (!attr) {
+			err = EPROTO;
+			goto out;
+		}
+	}
+
+	if (!sa_isset(&uc->maddr, SA_ALL)) {
+		uc->maddr = attr->v.sa;
+	}
+	else if (!sa_cmp(&uc->maddr, &attr->v.sa, SA_ALL)) {
+		err = ENOTCONN;
+		goto out;
+	}
+
+ out:
+	if (err) {
+		udpconn_close(uc, err);
+		mem_deref(uc);
+	}
+	else {
+		tmr_start(&uc->tmr_ka, sip_keepalive_wait(uc->ka_interval),
+			  udpconn_keepalive_handler, uc);
+	}
+}
+
+
+static void udpconn_keepalive_handler(void *arg)
+{
+	struct sip_udpconn *uc = arg;
+	int err;
+
+	if (!uc->kal.head) {
+		/* no need for us anymore */
+		udpconn_close(uc, 0);
+		mem_deref(uc);
+		return;
+	}
+
+	err = stun_request(&uc->ct, uc->stun, IPPROTO_UDP, uc->us,
+			   &uc->paddr, 0, STUN_METHOD_BINDING, NULL, 0,
+			   false, stun_response_handler, uc, 1,
+			   STUN_ATTR_SOFTWARE, stun_software);
+	if (err) {
+		udpconn_close(uc, err);
+		mem_deref(uc);
+	}
+}
+
+
+static struct sip_udpconn *udpconn_find(struct sip *sip, struct udp_sock *us,
+					const struct sa *paddr)
+{
+	struct le *le;
+
+	le = list_head(hash_list(sip->ht_udpconn, sa_hash(paddr, SA_ALL)));
+
+	for (; le; le = le->next) {
+
+		struct sip_udpconn *uc = le->data;
+
+		if (!sa_cmp(&uc->paddr, paddr, SA_ALL))
+			continue;
+
+		if (uc->us != us)
+			continue;
+
+		return uc;
+	}
+
+	return NULL;
+}
+
+
+int  sip_keepalive_udp(struct sip_keepalive *ka, struct sip *sip,
+		       struct udp_sock *us, const struct sa *paddr,
+		       uint32_t interval)
+{
+	struct sip_udpconn *uc;
+
+	if (!ka || !sip || !us || !paddr)
+		return EINVAL;
+
+	uc = udpconn_find(sip, us, paddr);
+	if (!uc) {
+		uc = mem_zalloc(sizeof(*uc), destructor);
+		if (!uc)
+			return ENOMEM;
+
+		hash_append(sip->ht_udpconn, sa_hash(paddr, SA_ALL),
+			    &uc->he, uc);
+
+		uc->paddr = *paddr;
+		uc->stun  = mem_ref(sip->stun);
+		uc->us    = mem_ref(us);
+		uc->ka_interval = interval ? interval : UDP_KEEPALIVE_INTVAL;
+
+		/* learn mapped address immediately */
+		tmr_start(&uc->tmr_ka, 0, udpconn_keepalive_handler, uc);
+	}
+
+	list_append(&uc->kal, &ka->le, ka);
+
+	return 0;
+}
diff --git a/src/sip/mod.mk b/src/sip/mod.mk
new file mode 100644
index 0000000..446c028
--- /dev/null
+++ b/src/sip/mod.mk
@@ -0,0 +1,21 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS	+= sip/addr.c
+SRCS	+= sip/auth.c
+SRCS	+= sip/contact.c
+SRCS	+= sip/cseq.c
+SRCS	+= sip/ctrans.c
+SRCS	+= sip/dialog.c
+SRCS	+= sip/keepalive.c
+SRCS	+= sip/keepalive_udp.c
+SRCS	+= sip/msg.c
+SRCS	+= sip/reply.c
+SRCS	+= sip/request.c
+SRCS	+= sip/sip.c
+SRCS	+= sip/strans.c
+SRCS	+= sip/transp.c
+SRCS	+= sip/via.c
diff --git a/src/sip/msg.c b/src/sip/msg.c
new file mode 100644
index 0000000..2647e0b
--- /dev/null
+++ b/src/sip/msg.c
@@ -0,0 +1,682 @@
+/**
+ * @file sip/msg.c  SIP Message decode
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <ctype.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_sys.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_udp.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+enum {
+	HDR_HASH_SIZE = 32,
+	STARTLINE_MAX = 8192,
+};
+
+
+static void hdr_destructor(void *arg)
+{
+	struct sip_hdr *hdr = arg;
+
+	list_unlink(&hdr->le);
+	hash_unlink(&hdr->he);
+}
+
+
+static void destructor(void *arg)
+{
+	struct sip_msg *msg = arg;
+
+	list_flush(&msg->hdrl);
+	hash_flush(msg->hdrht);
+	mem_deref(msg->hdrht);
+	mem_deref(msg->sock);
+	mem_deref(msg->mb);
+}
+
+
+static enum sip_hdrid hdr_hash(const struct pl *name)
+{
+	if (!name->l)
+		return SIP_HDR_NONE;
+
+	if (name->l > 1) {
+		switch (name->p[0]) {
+
+		case 'x':
+		case 'X':
+			if (name->p[1] == '-')
+				return SIP_HDR_NONE;
+
+			/*@fallthrough@*/
+
+		default:
+			return (enum sip_hdrid)
+				(hash_joaat_ci(name->p, name->l) & 0xfff);
+		}
+	}
+
+	/* compact headers */
+	switch (tolower(name->p[0])) {
+
+	case 'a': return SIP_HDR_ACCEPT_CONTACT;
+	case 'b': return SIP_HDR_REFERRED_BY;
+	case 'c': return SIP_HDR_CONTENT_TYPE;
+	case 'd': return SIP_HDR_REQUEST_DISPOSITION;
+	case 'e': return SIP_HDR_CONTENT_ENCODING;
+	case 'f': return SIP_HDR_FROM;
+	case 'i': return SIP_HDR_CALL_ID;
+	case 'j': return SIP_HDR_REJECT_CONTACT;
+	case 'k': return SIP_HDR_SUPPORTED;
+	case 'l': return SIP_HDR_CONTENT_LENGTH;
+	case 'm': return SIP_HDR_CONTACT;
+	case 'n': return SIP_HDR_IDENTITY_INFO;
+	case 'o': return SIP_HDR_EVENT;
+	case 'r': return SIP_HDR_REFER_TO;
+	case 's': return SIP_HDR_SUBJECT;
+	case 't': return SIP_HDR_TO;
+	case 'u': return SIP_HDR_ALLOW_EVENTS;
+	case 'v': return SIP_HDR_VIA;
+	case 'x': return SIP_HDR_SESSION_EXPIRES;
+	case 'y': return SIP_HDR_IDENTITY;
+	default:  return SIP_HDR_NONE;
+	}
+}
+
+
+static inline bool hdr_comma_separated(enum sip_hdrid id)
+{
+	switch (id) {
+
+	case SIP_HDR_ACCEPT:
+	case SIP_HDR_ACCEPT_CONTACT:
+	case SIP_HDR_ACCEPT_ENCODING:
+	case SIP_HDR_ACCEPT_LANGUAGE:
+	case SIP_HDR_ACCEPT_RESOURCE_PRIORITY:
+	case SIP_HDR_ALERT_INFO:
+	case SIP_HDR_ALLOW:
+	case SIP_HDR_ALLOW_EVENTS:
+	case SIP_HDR_AUTHENTICATION_INFO:
+	case SIP_HDR_CALL_INFO:
+	case SIP_HDR_CONTACT:
+	case SIP_HDR_CONTENT_ENCODING:
+	case SIP_HDR_CONTENT_LANGUAGE:
+	case SIP_HDR_ERROR_INFO:
+	case SIP_HDR_HISTORY_INFO:
+	case SIP_HDR_IN_REPLY_TO:
+	case SIP_HDR_P_ASSERTED_IDENTITY:
+	case SIP_HDR_P_ASSOCIATED_URI:
+	case SIP_HDR_P_EARLY_MEDIA:
+	case SIP_HDR_P_MEDIA_AUTHORIZATION:
+	case SIP_HDR_P_PREFERRED_IDENTITY:
+	case SIP_HDR_P_REFUSED_URI_LIST:
+	case SIP_HDR_P_VISITED_NETWORK_ID:
+	case SIP_HDR_PATH:
+	case SIP_HDR_PERMISSION_MISSING:
+	case SIP_HDR_PROXY_REQUIRE:
+	case SIP_HDR_REASON:
+	case SIP_HDR_RECORD_ROUTE:
+	case SIP_HDR_REJECT_CONTACT:
+	case SIP_HDR_REQUEST_DISPOSITION:
+	case SIP_HDR_REQUIRE:
+	case SIP_HDR_RESOURCE_PRIORITY:
+	case SIP_HDR_ROUTE:
+	case SIP_HDR_SECURITY_CLIENT:
+	case SIP_HDR_SECURITY_SERVER:
+	case SIP_HDR_SECURITY_VERIFY:
+	case SIP_HDR_SERVICE_ROUTE:
+	case SIP_HDR_SUPPORTED:
+	case SIP_HDR_TRIGGER_CONSENT:
+	case SIP_HDR_UNSUPPORTED:
+	case SIP_HDR_VIA:
+	case SIP_HDR_WARNING:
+		return true;
+
+	default:
+		return false;
+	}
+}
+
+
+static inline int hdr_add(struct sip_msg *msg, const struct pl *name,
+			  enum sip_hdrid id, const char *p, ssize_t l,
+			  bool atomic, bool line)
+{
+	struct sip_hdr *hdr;
+	int err = 0;
+
+	hdr = mem_zalloc(sizeof(*hdr), hdr_destructor);
+	if (!hdr)
+		return ENOMEM;
+
+	hdr->name  = *name;
+	hdr->val.p = p;
+	hdr->val.l = MAX(l, 0);
+	hdr->id    = id;
+
+	switch (id) {
+
+	case SIP_HDR_VIA:
+	case SIP_HDR_ROUTE:
+		if (!atomic)
+			break;
+
+		hash_append(msg->hdrht, id, &hdr->he, mem_ref(hdr));
+		list_append(&msg->hdrl, &hdr->le, mem_ref(hdr));
+		break;
+
+	default:
+		if (atomic)
+			hash_append(msg->hdrht, id, &hdr->he, mem_ref(hdr));
+		if (line)
+			list_append(&msg->hdrl, &hdr->le, mem_ref(hdr));
+		break;
+	}
+
+	/* parse common headers */
+	switch (id) {
+
+	case SIP_HDR_VIA:
+		if (!atomic || pl_isset(&msg->via.sentby))
+			break;
+
+		err = sip_via_decode(&msg->via, &hdr->val);
+		break;
+
+	case SIP_HDR_TO:
+		err = sip_addr_decode((struct sip_addr *)&msg->to, &hdr->val);
+		if (err)
+			break;
+
+		(void)msg_param_decode(&msg->to.params, "tag", &msg->to.tag);
+		msg->to.val = hdr->val;
+		break;
+
+	case SIP_HDR_FROM:
+		err = sip_addr_decode((struct sip_addr *)&msg->from,
+				      &hdr->val);
+		if (err)
+			break;
+
+		(void)msg_param_decode(&msg->from.params, "tag",
+				       &msg->from.tag);
+		msg->from.val = hdr->val;
+		break;
+
+	case SIP_HDR_CALL_ID:
+		msg->callid = hdr->val;
+		break;
+
+	case SIP_HDR_CSEQ:
+		err = sip_cseq_decode(&msg->cseq, &hdr->val);
+		break;
+
+	case SIP_HDR_MAX_FORWARDS:
+		msg->maxfwd = hdr->val;
+		break;
+
+	case SIP_HDR_CONTENT_TYPE:
+		err = msg_ctype_decode(&msg->ctyp, &hdr->val);
+		break;
+
+	case SIP_HDR_CONTENT_LENGTH:
+		msg->clen = hdr->val;
+		break;
+
+	case SIP_HDR_EXPIRES:
+		msg->expires = hdr->val;
+		break;
+
+	default:
+		/* re_printf("%r = %u\n", &hdr->name, id); */
+		break;
+	}
+
+	mem_deref(hdr);
+
+	return err;
+}
+
+
+/**
+ * Decode a SIP message
+ *
+ * @param msgp Pointer to allocated SIP Message
+ * @param mb   Buffer containing SIP Message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_msg_decode(struct sip_msg **msgp, struct mbuf *mb)
+{
+	struct pl x, y, z, e, name;
+	const char *p, *v, *cv;
+	struct sip_msg *msg;
+	bool comsep, quote;
+	enum sip_hdrid id = SIP_HDR_NONE;
+	uint32_t ws, lf;
+	size_t l;
+	int err;
+
+	if (!msgp || !mb)
+		return EINVAL;
+
+	p = (const char *)mbuf_buf(mb);
+	l = mbuf_get_left(mb);
+
+	if (re_regex(p, l, "[^ \t\r\n]+ [^ \t\r\n]+ [^\r\n]*[\r]*[\n]1",
+		     &x, &y, &z, NULL, &e) || x.p != (char *)mbuf_buf(mb))
+		return (l > STARTLINE_MAX) ? EBADMSG : ENODATA;
+
+	msg = mem_zalloc(sizeof(*msg), destructor);
+	if (!msg)
+		return ENOMEM;
+
+	err = hash_alloc(&msg->hdrht, HDR_HASH_SIZE);
+	if (err)
+		goto out;
+
+	msg->tag = rand_u64();
+	msg->mb  = mem_ref(mb);
+	msg->req = (0 == pl_strcmp(&z, "SIP/2.0"));
+
+	if (msg->req) {
+
+		msg->met = x;
+		msg->ruri = y;
+		msg->ver = z;
+
+		if (uri_decode(&msg->uri, &y)) {
+			err = EBADMSG;
+			goto out;
+		}
+	}
+	else {
+		msg->ver    = x;
+		msg->scode  = pl_u32(&y);
+		msg->reason = z;
+
+		if (!msg->scode) {
+			err = EBADMSG;
+			goto out;
+		}
+	}
+
+	l -= e.p + e.l - p;
+	p = e.p + e.l;
+
+	name.p = v = cv = NULL;
+	name.l = ws = lf = 0;
+	comsep = false;
+	quote = false;
+
+	for (; l > 0; p++, l--) {
+
+		switch (*p) {
+
+		case ' ':
+		case '\t':
+			lf = 0; /* folding */
+			++ws;
+			break;
+
+		case '\r':
+			++ws;
+			break;
+
+		case '\n':
+			++ws;
+
+			if (!lf++)
+				break;
+
+			++p; --l; /* eoh */
+
+			/*@fallthrough@*/
+
+		default:
+			if (lf || (*p == ',' && comsep && !quote)) {
+
+				if (!name.l) {
+					err = EBADMSG;
+					goto out;
+				}
+
+				err = hdr_add(msg, &name, id, cv ? cv : p,
+					      cv ? p - cv - ws : 0,
+					      true, cv == v && lf);
+				if (err)
+					goto out;
+
+				if (!lf) { /* comma separated */
+					cv = NULL;
+					break;
+				}
+
+				if (cv != v) {
+					err = hdr_add(msg, &name, id,
+						      v ? v : p,
+						      v ? p - v - ws : 0,
+						      false, true);
+					if (err)
+						goto out;
+				}
+
+				if (lf > 1) { /* eoh */
+					err = 0;
+					goto out;
+				}
+
+				comsep = false;
+				name.p = NULL;
+				cv = v = NULL;
+				lf = 0;
+			}
+
+			if (!name.p) {
+				name.p = p;
+				name.l = 0;
+				ws = 0;
+			}
+
+			if (!name.l) {
+				if (*p != ':') {
+					ws = 0;
+					break;
+				}
+
+				name.l = MAX((int)(p - name.p - ws), 0);
+				if (!name.l) {
+					err = EBADMSG;
+					goto out;
+				}
+
+				id = hdr_hash(&name);
+				comsep = hdr_comma_separated(id);
+				break;
+			}
+
+			if (!cv) {
+				quote = false;
+				cv = p;
+			}
+
+			if (!v) {
+				v = p;
+			}
+
+			if (*p == '"')
+				quote = !quote;
+
+			ws = 0;
+			break;
+		}
+	}
+
+	err = ENODATA;
+
+ out:
+	if (err)
+		mem_deref(msg);
+	else {
+		*msgp = msg;
+		mb->pos = mb->end - l;
+	}
+
+	return err;
+}
+
+
+/**
+ * Get a SIP Header from a SIP Message
+ *
+ * @param msg SIP Message
+ * @param id  SIP Header ID
+ *
+ * @return SIP Header if found, NULL if not found
+ */
+const struct sip_hdr *sip_msg_hdr(const struct sip_msg *msg, enum sip_hdrid id)
+{
+	return sip_msg_hdr_apply(msg, true, id, NULL, NULL);
+}
+
+
+/**
+ * Apply a function handler to certain SIP Headers
+ *
+ * @param msg SIP Message
+ * @param fwd True to traverse forwards, false to traverse backwards
+ * @param id  SIP Header ID
+ * @param h   Function handler
+ * @param arg Handler argument
+ *
+ * @return SIP Header if handler returns true, otherwise NULL
+ */
+const struct sip_hdr *sip_msg_hdr_apply(const struct sip_msg *msg,
+					bool fwd, enum sip_hdrid id,
+					sip_hdr_h *h, void *arg)
+{
+	struct list *lst;
+	struct le *le;
+
+	if (!msg)
+		return NULL;
+
+	lst = hash_list(msg->hdrht, id);
+
+	le = fwd ? list_head(lst) : list_tail(lst);
+
+	while (le) {
+		const struct sip_hdr *hdr = le->data;
+
+		le = fwd ? le->next : le->prev;
+
+		if (hdr->id != id)
+			continue;
+
+		if (!h || h(hdr, msg, arg))
+			return hdr;
+	}
+
+	return NULL;
+}
+
+
+/**
+ * Get an unknown SIP Header from a SIP Message
+ *
+ * @param msg  SIP Message
+ * @param name Header name
+ *
+ * @return SIP Header if found, NULL if not found
+ */
+const struct sip_hdr *sip_msg_xhdr(const struct sip_msg *msg, const char *name)
+{
+	return sip_msg_xhdr_apply(msg, true, name, NULL, NULL);
+}
+
+
+/**
+ * Apply a function handler to certain unknown SIP Headers
+ *
+ * @param msg  SIP Message
+ * @param fwd  True to traverse forwards, false to traverse backwards
+ * @param name SIP Header name
+ * @param h    Function handler
+ * @param arg  Handler argument
+ *
+ * @return SIP Header if handler returns true, otherwise NULL
+ */
+const struct sip_hdr *sip_msg_xhdr_apply(const struct sip_msg *msg,
+					 bool fwd, const char *name,
+					 sip_hdr_h *h, void *arg)
+{
+	struct list *lst;
+	struct le *le;
+	struct pl pl;
+
+	if (!msg || !name)
+		return NULL;
+
+	pl_set_str(&pl, name);
+
+	lst = hash_list(msg->hdrht, hdr_hash(&pl));
+
+	le = fwd ? list_head(lst) : list_tail(lst);
+
+	while (le) {
+		const struct sip_hdr *hdr = le->data;
+
+		le = fwd ? le->next : le->prev;
+
+		if (pl_casecmp(&hdr->name, &pl))
+			continue;
+
+		if (!h || h(hdr, msg, arg))
+			return hdr;
+	}
+
+	return NULL;
+}
+
+
+static bool count_handler(const struct sip_hdr *hdr, const struct sip_msg *msg,
+			  void *arg)
+{
+	uint32_t *n = arg;
+	(void)hdr;
+	(void)msg;
+
+	++(*n);
+
+	return false;
+}
+
+
+/**
+ * Count the number of SIP Headers
+ *
+ * @param msg SIP Message
+ * @param id  SIP Header ID
+ *
+ * @return Number of SIP Headers
+ */
+uint32_t sip_msg_hdr_count(const struct sip_msg *msg, enum sip_hdrid id)
+{
+	uint32_t n = 0;
+
+	sip_msg_hdr_apply(msg, true, id, count_handler, &n);
+
+	return n;
+}
+
+
+/**
+ * Count the number of unknown SIP Headers
+ *
+ * @param msg  SIP Message
+ * @param name SIP Header name
+ *
+ * @return Number of SIP Headers
+ */
+uint32_t sip_msg_xhdr_count(const struct sip_msg *msg, const char *name)
+{
+	uint32_t n = 0;
+
+	sip_msg_xhdr_apply(msg, true, name, count_handler, &n);
+
+	return n;
+}
+
+
+static bool value_handler(const struct sip_hdr *hdr, const struct sip_msg *msg,
+			  void *arg)
+{
+	(void)msg;
+
+	return 0 == pl_strcasecmp(&hdr->val, (const char *)arg);
+}
+
+
+/**
+ * Check if a SIP Header matches a certain value
+ *
+ * @param msg   SIP Message
+ * @param id    SIP Header ID
+ * @param value Header value to check
+ *
+ * @return True if value matches, false if not
+ */
+bool sip_msg_hdr_has_value(const struct sip_msg *msg, enum sip_hdrid id,
+			   const char *value)
+{
+	return NULL != sip_msg_hdr_apply(msg, true, id, value_handler,
+					 (void *)value);
+}
+
+
+/**
+ * Check if an unknown SIP Header matches a certain value
+ *
+ * @param msg   SIP Message
+ * @param name  SIP Header name
+ * @param value Header value to check
+ *
+ * @return True if value matches, false if not
+ */
+bool sip_msg_xhdr_has_value(const struct sip_msg *msg, const char *name,
+			    const char *value)
+{
+	return NULL != sip_msg_xhdr_apply(msg, true, name, value_handler,
+					  (void *)value);
+}
+
+
+/**
+ * Print a SIP Message to stdout
+ *
+ * @param msg SIP Message
+ */
+void sip_msg_dump(const struct sip_msg *msg)
+{
+	struct le *le;
+	uint32_t i;
+
+	if (!msg)
+		return;
+
+	for (i=0; i<HDR_HASH_SIZE; i++) {
+
+		le = list_head(hash_list(msg->hdrht, i));
+
+		while (le) {
+			const struct sip_hdr *hdr = le->data;
+
+			le = le->next;
+
+			(void)re_printf("%02u '%r'='%r'\n", i, &hdr->name,
+					&hdr->val);
+		}
+	}
+
+	le = list_head(&msg->hdrl);
+
+	while (le) {
+		const struct sip_hdr *hdr = le->data;
+
+		le = le->next;
+
+		(void)re_printf("%02u '%r'='%r'\n", hdr->id, &hdr->name,
+				&hdr->val);
+	}
+}
diff --git a/src/sip/reply.c b/src/sip/reply.c
new file mode 100644
index 0000000..eb2e26f
--- /dev/null
+++ b/src/sip/reply.c
@@ -0,0 +1,268 @@
+/**
+ * @file sip/reply.c  SIP Reply
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_udp.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+static int vreplyf(struct sip_strans **stp, struct mbuf **mbp, bool trans,
+		   struct sip *sip, const struct sip_msg *msg, bool rec_route,
+		   uint16_t scode, const char *reason,
+		   const char *fmt, va_list ap)
+{
+	bool rport = false;
+	uint32_t viac = 0;
+	struct mbuf *mb;
+	struct sa dst;
+	struct le *le;
+	int err;
+
+	if (!sip || !msg || !reason)
+		return EINVAL;
+
+	if (!pl_strcmp(&msg->met, "ACK"))
+		return 0;
+
+	mb = mbuf_alloc(1024);
+	if (!mb) {
+		err = ENOMEM;
+		goto out;
+	}
+
+	err = mbuf_printf(mb, "SIP/2.0 %u %s\r\n", scode, reason);
+
+	for (le = msg->hdrl.head; le; le = le->next) {
+
+		struct sip_hdr *hdr = le->data;
+		struct pl rp;
+
+		switch (hdr->id) {
+
+		case SIP_HDR_VIA:
+			err |= mbuf_printf(mb, "%r: ", &hdr->name);
+			if (viac++) {
+				err |= mbuf_printf(mb, "%r\r\n", &hdr->val);
+				break;
+			}
+
+			if (!msg_param_exists(&msg->via.params, "rport", &rp)){
+				err |= mbuf_write_pl_skip(mb, &hdr->val, &rp);
+				err |= mbuf_printf(mb, ";rport=%u",
+						   sa_port(&msg->src));
+				rport = true;
+			}
+			else
+				err |= mbuf_write_pl(mb, &hdr->val);
+
+			if (rport || !sa_cmp(&msg->src, &msg->via.addr,
+					     SA_ADDR))
+				err |= mbuf_printf(mb, ";received=%j",
+						   &msg->src);
+
+			err |= mbuf_write_str(mb, "\r\n");
+			break;
+
+		case SIP_HDR_TO:
+			err |= mbuf_printf(mb, "%r: %r", &hdr->name,
+					   &hdr->val);
+			if (!pl_isset(&msg->to.tag) && scode > 100)
+				err |= mbuf_printf(mb, ";tag=%016llx",
+						   msg->tag);
+			err |= mbuf_write_str(mb, "\r\n");
+			break;
+
+		case SIP_HDR_RECORD_ROUTE:
+			if (!rec_route)
+				break;
+
+			/*@fallthrough@*/
+
+		case SIP_HDR_FROM:
+		case SIP_HDR_CALL_ID:
+		case SIP_HDR_CSEQ:
+			err |= mbuf_printf(mb, "%r: %r\r\n",
+					   &hdr->name, &hdr->val);
+			break;
+
+		default:
+			break;
+		}
+	}
+
+	if (sip->software)
+		err |= mbuf_printf(mb, "Server: %s\r\n", sip->software);
+
+	if (fmt)
+		err |= mbuf_vprintf(mb, fmt, ap);
+	else
+		err |= mbuf_printf(mb, "Content-Length: 0\r\n\r\n");
+
+	if (err)
+		goto out;
+
+	mb->pos = 0;
+
+	sip_reply_addr(&dst, msg, rport);
+
+	if (trans) {
+		err = sip_strans_reply(stp, sip, msg, &dst, scode, mb);
+	}
+	else {
+		err = sip_send(sip, msg->sock, msg->tp, &dst, mb);
+	}
+
+ out:
+	if (err && stp)
+		*stp = mem_deref(*stp);
+
+	if (!err && mbp)
+		*mbp = mb;
+	else
+		mem_deref(mb);
+
+	return err;
+}
+
+
+/**
+ * Formatted reply using Server Transaction
+ *
+ * @param stp       Pointer to allocated SIP Server Transaction (optional)
+ * @param mbp       Pointer to allocated SIP message buffer (optional)
+ * @param sip       SIP Stack instance
+ * @param msg       Incoming SIP message
+ * @param rec_route True to copy Record-Route headers
+ * @param scode     Response status code
+ * @param reason    Response reason phrase
+ * @param fmt       Additional formatted SIP headers and body, otherwise NULL
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_treplyf(struct sip_strans **stp, struct mbuf **mbp, struct sip *sip,
+		const struct sip_msg *msg, bool rec_route, uint16_t scode,
+		const char *reason, const char *fmt, ...)
+{
+	va_list ap;
+	int err;
+
+	va_start(ap, fmt);
+	err = vreplyf(stp, mbp, true, sip, msg, rec_route, scode, reason,
+		      fmt, ap);
+	va_end(ap);
+
+	return err;
+}
+
+
+/**
+ * Reply using Server Transaction
+ *
+ * @param stp    Pointer to allocated SIP Server Transaction (optional)
+ * @param sip    SIP Stack instance
+ * @param msg    Incoming SIP message
+ * @param scode  Response status code
+ * @param reason Response reason phrase
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_treply(struct sip_strans **stp, struct sip *sip,
+	       const struct sip_msg *msg, uint16_t scode, const char *reason)
+{
+	return sip_treplyf(stp, NULL, sip, msg, false, scode, reason, NULL);
+}
+
+
+/**
+ * Stateless formatted reply
+ *
+ * @param sip    SIP Stack instance
+ * @param msg    Incoming SIP message
+ * @param scode  Response status code
+ * @param reason Response reason phrase
+ * @param fmt    Additional formatted SIP headers and body, otherwise NULL
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_replyf(struct sip *sip, const struct sip_msg *msg, uint16_t scode,
+	       const char *reason, const char *fmt, ...)
+{
+	va_list ap;
+	int err;
+
+	va_start(ap, fmt);
+	err = vreplyf(NULL, NULL, false, sip, msg, false, scode, reason,
+		      fmt, ap);
+	va_end(ap);
+
+	return err;
+}
+
+
+/**
+ * Stateless reply
+ *
+ * @param sip    SIP Stack instance
+ * @param msg    Incoming SIP message
+ * @param scode  Response status code
+ * @param reason Response reason phrase
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_reply(struct sip *sip, const struct sip_msg *msg, uint16_t scode,
+	      const char *reason)
+{
+	return sip_replyf(sip, msg, scode, reason, NULL);
+}
+
+
+/**
+ * Get the reply address from a SIP message
+ *
+ * @param addr  Network address, set on return
+ * @param msg   SIP message
+ * @param rport Rport value
+ */
+void sip_reply_addr(struct sa *addr, const struct sip_msg *msg, bool rport)
+{
+	uint16_t port;
+	struct pl pl;
+
+	if (!addr || !msg)
+		return;
+
+	port = sa_port(&msg->via.addr);
+	*addr = msg->src;
+
+	switch (msg->tp) {
+
+	case SIP_TRANSP_UDP:
+		if (!msg_param_decode(&msg->via.params, "maddr", &pl)) {
+			(void)sa_set(addr, &pl,sip_transp_port(msg->tp, port));
+			break;
+		}
+
+		if (rport)
+			break;
+
+		/*@fallthrough@*/
+
+	case SIP_TRANSP_TCP:
+	case SIP_TRANSP_TLS:
+		sa_set_port(addr, sip_transp_port(msg->tp, port));
+		break;
+
+	default:
+		break;
+	}
+}
diff --git a/src/sip/request.c b/src/sip/request.c
new file mode 100644
index 0000000..aca2935
--- /dev/null
+++ b/src/sip/request.c
@@ -0,0 +1,936 @@
+/**
+ * @file sip/request.c  SIP Request
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_fmt.h>
+#include <re_dns.h>
+#include <re_uri.h>
+#include <re_sys.h>
+#include <re_udp.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+struct sip_request {
+	struct le le;
+	struct list cachel;
+	struct list addrl;
+	struct list srvl;
+	struct sip_request **reqp;
+	struct sip_ctrans *ct;
+	struct dns_query *dnsq;
+	struct dns_query *dnsq2;
+	struct sip *sip;
+	char *met;
+	char *uri;
+	char *host;
+	struct mbuf *mb;
+	sip_send_h *sendh;
+	sip_resp_h *resph;
+	void *arg;
+	size_t sortkey;
+	enum sip_transp tp;
+	bool tp_selected;
+	bool stateful;
+	bool canceled;
+	bool provrecv;
+	uint16_t port;
+};
+
+
+static int  request_next(struct sip_request *req);
+static bool rr_append_handler(struct dnsrr *rr, void *arg);
+static void srv_handler(int err, const struct dnshdr *hdr, struct list *ansl,
+			struct list *authl, struct list *addl, void *arg);
+static int  srv_lookup(struct sip_request *req, const char *domain);
+static int  addr_lookup(struct sip_request *req, const char *name);
+
+
+static int str_ldup(char **dst, const char *src, int len)
+{
+	struct pl pl;
+
+	pl.p = src;
+	pl.l = len < 0 ? str_len(src) : (size_t)len;
+
+	return pl_strdup(dst, &pl);
+}
+
+
+static void destructor(void *arg)
+{
+	struct sip_request *req = arg;
+
+	if (req->reqp && req->stateful) {
+		/* user does deref before request has completed */
+		*req->reqp = NULL;
+		req->reqp  = NULL;
+		req->sendh = NULL;
+		req->resph = NULL;
+		sip_request_cancel(mem_ref(req));
+		return;
+	}
+
+	list_flush(&req->cachel);
+	list_flush(&req->addrl);
+	list_flush(&req->srvl);
+	list_unlink(&req->le);
+	mem_deref(req->dnsq);
+	mem_deref(req->dnsq2);
+	mem_deref(req->ct);
+	mem_deref(req->met);
+	mem_deref(req->uri);
+	mem_deref(req->host);
+	mem_deref(req->mb);
+}
+
+
+static void terminate(struct sip_request *req, int err,
+		      const struct sip_msg *msg)
+{
+	if (req->reqp) {
+		*req->reqp = NULL;
+		req->reqp = NULL;
+	}
+
+	list_unlink(&req->le);
+	req->sendh = NULL;
+
+	if (req->resph) {
+		req->resph(err, msg, req->arg);
+		req->resph = NULL;
+	}
+}
+
+
+static bool close_handler(struct le *le, void *arg)
+{
+	struct sip_request *req = le->data;
+	(void)arg;
+
+	req->dnsq  = mem_deref(req->dnsq);
+	req->dnsq2 = mem_deref(req->dnsq2);
+	req->ct    = mem_deref(req->ct);
+
+	terminate(req, ECONNABORTED, NULL);
+	mem_deref(req);
+
+	return false;
+}
+
+
+static void response_handler(int err, const struct sip_msg *msg, void *arg)
+{
+	struct sip_request *req = arg;
+
+	if (msg && msg->scode < 200) {
+		if (!req->provrecv) {
+			req->provrecv = true;
+			if (req->canceled)
+				(void)sip_ctrans_cancel(req->ct);
+		}
+
+		if (req->resph)
+			req->resph(err, msg, req->arg);
+
+		return;
+	}
+
+	req->ct = NULL;
+
+	if (!req->canceled && (err || msg->scode == 503) &&
+	    (req->addrl.head || req->srvl.head)) {
+
+		err = request_next(req);
+		if (!err)
+			return;
+	}
+
+	terminate(req, err, msg);
+	mem_deref(req);
+}
+
+
+static int request(struct sip_request *req, enum sip_transp tp,
+		   const struct sa *dst)
+{
+	struct mbuf *mb = NULL;
+	char *branch = NULL;
+	int err = ENOMEM;
+	struct sa laddr;
+
+	req->provrecv = false;
+
+	branch = mem_alloc(24, NULL);
+	mb = mbuf_alloc(1024);
+
+	if (!branch || !mb)
+		goto out;
+
+	(void)re_snprintf(branch, 24, "z9hG4bK%016llx", rand_u64());
+
+	err = sip_transp_laddr(req->sip, &laddr, tp, dst);
+	if (err)
+		goto out;
+
+	err  = mbuf_printf(mb, "%s %s SIP/2.0\r\n", req->met, req->uri);
+	err |= mbuf_printf(mb, "Via: SIP/2.0/%s %J;branch=%s;rport\r\n",
+			   sip_transp_name(tp), &laddr, branch);
+	err |= req->sendh ? req->sendh(tp, &laddr, dst, mb, req->arg) : 0;
+	err |= mbuf_write_mem(mb, mbuf_buf(req->mb), mbuf_get_left(req->mb));
+	if (err)
+		goto out;
+
+	mb->pos = 0;
+
+	if (!req->stateful)
+		err = sip_send(req->sip, NULL, tp, dst, mb);
+	else
+		err = sip_ctrans_request(&req->ct, req->sip, tp, dst, req->met,
+					 branch, mb, response_handler, req);
+	if (err)
+		goto out;
+
+ out:
+	mem_deref(branch);
+	mem_deref(mb);
+
+	return err;
+}
+
+
+static int request_next(struct sip_request *req)
+{
+	struct dnsrr *rr;
+	struct sa dst;
+	int err;
+
+ again:
+	rr = list_ledata(req->addrl.head);
+	if (!rr) {
+		rr = list_ledata(req->srvl.head);
+		if (!rr)
+			return ENOENT;
+
+		req->port = rr->rdata.srv.port;
+
+		dns_rrlist_apply2(&req->cachel, rr->rdata.srv.target,
+				  DNS_TYPE_A, DNS_TYPE_AAAA, DNS_CLASS_IN,
+				  true, rr_append_handler, &req->addrl);
+
+		list_unlink(&rr->le);
+
+		if (req->addrl.head) {
+			dns_rrlist_sort_addr(&req->addrl, req->sortkey);
+			mem_deref(rr);
+			goto again;
+		}
+
+		err = addr_lookup(req, rr->rdata.srv.target);
+		mem_deref(rr);
+
+		return err;
+	}
+
+	switch (rr->type) {
+
+	case DNS_TYPE_A:
+		sa_set_in(&dst, rr->rdata.a.addr, req->port);
+		break;
+
+	case DNS_TYPE_AAAA:
+		sa_set_in6(&dst, rr->rdata.aaaa.addr, req->port);
+		break;
+
+	default:
+		return EINVAL;
+	}
+
+	list_unlink(&rr->le);
+	mem_deref(rr);
+
+	err = request(req, req->tp, &dst);
+	if (err) {
+		if (req->addrl.head || req->srvl.head)
+			goto again;
+	}
+	else if (!req->stateful) {
+		req->resph = NULL;
+		terminate(req, 0, NULL);
+		mem_deref(req);
+	}
+
+	return err;
+}
+
+
+static bool transp_next(struct sip *sip, enum sip_transp *tp)
+{
+	enum sip_transp i;
+
+	for (i=(enum sip_transp)(*tp+1); i<SIP_TRANSPC; i++) {
+
+		if (!sip_transp_supported(sip, i, AF_UNSPEC))
+			continue;
+
+		*tp = i;
+		return true;
+	}
+
+	return false;
+}
+
+
+static bool transp_next_srv(struct sip *sip, enum sip_transp *tp)
+{
+	enum sip_transp i;
+
+	for (i=(enum sip_transp)(*tp-1); i>SIP_TRANSP_NONE; i--) {
+
+		if (!sip_transp_supported(sip, i, AF_UNSPEC))
+			continue;
+
+		*tp = i;
+		return true;
+	}
+
+	return false;
+}
+
+
+static bool rr_append_handler(struct dnsrr *rr, void *arg)
+{
+	struct list *lst = arg;
+
+	switch (rr->type) {
+
+	case DNS_TYPE_A:
+	case DNS_TYPE_AAAA:
+	case DNS_TYPE_SRV:
+		if (rr->le.list)
+			break;
+
+		list_append(lst, &rr->le, mem_ref(rr));
+		break;
+	}
+
+	return false;
+}
+
+
+static bool rr_cache_handler(struct dnsrr *rr, void *arg)
+{
+	struct sip_request *req = arg;
+
+	switch (rr->type) {
+
+	case DNS_TYPE_A:
+		if (!sip_transp_supported(req->sip, req->tp, AF_INET))
+			break;
+
+		list_unlink(&rr->le_priv);
+		list_append(&req->cachel, &rr->le_priv, rr);
+		break;
+
+#ifdef HAVE_INET6
+	case DNS_TYPE_AAAA:
+		if (!sip_transp_supported(req->sip, req->tp, AF_INET6))
+			break;
+
+		list_unlink(&rr->le_priv);
+		list_append(&req->cachel, &rr->le_priv, rr);
+		break;
+#endif
+
+	case DNS_TYPE_CNAME:
+		list_unlink(&rr->le_priv);
+		list_append(&req->cachel, &rr->le_priv, rr);
+		break;
+	}
+
+	return false;
+}
+
+
+static bool rr_naptr_handler(struct dnsrr *rr, void *arg)
+{
+	struct sip_request *req = arg;
+	enum sip_transp tp;
+
+	if (rr->type != DNS_TYPE_NAPTR)
+		return false;
+
+	if (!str_casecmp(rr->rdata.naptr.services, "SIP+D2U"))
+		tp = SIP_TRANSP_UDP;
+	else if (!str_casecmp(rr->rdata.naptr.services, "SIP+D2T"))
+		tp = SIP_TRANSP_TCP;
+	else if (!str_casecmp(rr->rdata.naptr.services, "SIPS+D2T"))
+		tp = SIP_TRANSP_TLS;
+	else
+		return false;
+
+	if (!sip_transp_supported(req->sip, tp, AF_UNSPEC))
+		return false;
+
+	req->tp = tp;
+	req->tp_selected = true;
+
+	return true;
+}
+
+
+static void naptr_handler(int err, const struct dnshdr *hdr, struct list *ansl,
+			  struct list *authl, struct list *addl, void *arg)
+{
+	struct sip_request *req = arg;
+	struct dnsrr *rr;
+	(void)hdr;
+	(void)authl;
+
+	dns_rrlist_sort(ansl, DNS_TYPE_NAPTR, req->sortkey);
+
+	rr = dns_rrlist_apply(ansl, NULL, DNS_TYPE_NAPTR, DNS_CLASS_IN, false,
+			      rr_naptr_handler, req);
+	if (!rr) {
+		req->tp = SIP_TRANSPC;
+		if (!transp_next_srv(req->sip, &req->tp)) {
+			err = EPROTONOSUPPORT;
+			goto fail;
+		}
+
+		err = srv_lookup(req, req->host);
+		if (err)
+			goto fail;
+
+		return;
+	}
+
+	dns_rrlist_apply(addl, rr->rdata.naptr.replace, DNS_TYPE_SRV,
+			 DNS_CLASS_IN, true, rr_append_handler, &req->srvl);
+
+	if (!req->srvl.head) {
+		err = dnsc_query(&req->dnsq, req->sip->dnsc,
+				 rr->rdata.naptr.replace, DNS_TYPE_SRV,
+				 DNS_CLASS_IN, true, srv_handler, req);
+		if (err)
+			goto fail;
+
+		return;
+	}
+
+	dns_rrlist_sort(&req->srvl, DNS_TYPE_SRV, req->sortkey);
+
+	dns_rrlist_apply(addl, NULL, DNS_QTYPE_ANY, DNS_CLASS_IN, false,
+			 rr_cache_handler, req);
+
+	err = request_next(req);
+	if (err)
+		goto fail;
+
+	return;
+
+ fail:
+	terminate(req, err, NULL);
+	mem_deref(req);
+}
+
+
+static void srv_handler(int err, const struct dnshdr *hdr, struct list *ansl,
+			 struct list *authl, struct list *addl, void *arg)
+{
+	struct sip_request *req = arg;
+	(void)hdr;
+	(void)authl;
+
+	dns_rrlist_apply(ansl, NULL, DNS_TYPE_SRV, DNS_CLASS_IN, false,
+			 rr_append_handler, &req->srvl);
+
+	if (!req->srvl.head) {
+		if (!req->tp_selected) {
+			if (transp_next_srv(req->sip, &req->tp)) {
+
+				err = srv_lookup(req, req->host);
+				if (err)
+					goto fail;
+
+				return;
+			}
+
+			req->tp = SIP_TRANSP_NONE;
+			if (!transp_next(req->sip, &req->tp)) {
+				err = EPROTONOSUPPORT;
+				goto fail;
+			}
+		}
+
+		req->port = sip_transp_port(req->tp, 0);
+
+		err = addr_lookup(req, req->host);
+		if (err)
+			goto fail;
+
+		return;
+	}
+
+	dns_rrlist_sort(&req->srvl, DNS_TYPE_SRV, req->sortkey);
+
+	dns_rrlist_apply(addl, NULL, DNS_QTYPE_ANY, DNS_CLASS_IN, false,
+			 rr_cache_handler, req);
+
+	err = request_next(req);
+	if (err)
+		goto fail;
+
+	return;
+
+ fail:
+	terminate(req, err, NULL);
+	mem_deref(req);
+}
+
+
+static void addr_handler(int err, const struct dnshdr *hdr, struct list *ansl,
+			 struct list *authl, struct list *addl, void *arg)
+{
+	struct sip_request *req = arg;
+	(void)hdr;
+	(void)authl;
+	(void)addl;
+
+	dns_rrlist_apply2(ansl, NULL, DNS_TYPE_A, DNS_TYPE_AAAA, DNS_CLASS_IN,
+			  false, rr_append_handler, &req->addrl);
+
+	/* wait for other (A/AAAA) query to complete */
+	if (req->dnsq || req->dnsq2)
+		return;
+
+	if (!req->addrl.head && !req->srvl.head) {
+		err = err ? err : EDESTADDRREQ;
+		goto fail;
+	}
+
+	dns_rrlist_sort_addr(&req->addrl, req->sortkey);
+
+	err = request_next(req);
+	if (err)
+		goto fail;
+
+	return;
+
+ fail:
+	terminate(req, err, NULL);
+	mem_deref(req);
+}
+
+
+static int srv_lookup(struct sip_request *req, const char *domain)
+{
+	char name[256];
+
+	if (re_snprintf(name, sizeof(name), "%s.%s",
+			sip_transp_srvid(req->tp), domain) < 0)
+		return ENOMEM;
+
+	return dnsc_query(&req->dnsq, req->sip->dnsc, name, DNS_TYPE_SRV,
+			  DNS_CLASS_IN, true, srv_handler, req);
+}
+
+
+static int addr_lookup(struct sip_request *req, const char *name)
+{
+	int err;
+
+	if (sip_transp_supported(req->sip, req->tp, AF_INET)) {
+
+		err = dnsc_query(&req->dnsq, req->sip->dnsc, name,
+				 DNS_TYPE_A, DNS_CLASS_IN, true,
+				 addr_handler, req);
+		if (err)
+			return err;
+	}
+
+#ifdef HAVE_INET6
+	if (sip_transp_supported(req->sip, req->tp, AF_INET6)) {
+
+		err = dnsc_query(&req->dnsq2, req->sip->dnsc, name,
+				 DNS_TYPE_AAAA, DNS_CLASS_IN, true,
+				 addr_handler, req);
+		if (err)
+			return err;
+	}
+#endif
+
+	if (!req->dnsq && !req->dnsq2)
+		return EPROTONOSUPPORT;
+
+	return 0;
+}
+
+
+/**
+ * Send a SIP request
+ *
+ * @param reqp     Pointer to allocated SIP request object
+ * @param sip      SIP Stack
+ * @param stateful Stateful client transaction
+ * @param met      SIP Method string
+ * @param metl     Length of SIP Method string
+ * @param uri      Request URI
+ * @param uril     Length of Request URI string
+ * @param route    Next hop route URI
+ * @param mb       Buffer containing SIP request
+ * @param sortkey  Key for DNS record sorting
+ * @param sendh    Send handler
+ * @param resph    Response handler
+ * @param arg      Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_request(struct sip_request **reqp, struct sip *sip, bool stateful,
+		const char *met, int metl, const char *uri, int uril,
+		const struct uri *route, struct mbuf *mb, size_t sortkey,
+		sip_send_h *sendh, sip_resp_h *resph, void *arg)
+{
+	struct sip_request *req;
+	struct sa dst;
+	struct pl pl;
+	int err;
+
+	if (!sip || !met || !uri || !route || !mb)
+		return EINVAL;
+
+	if (pl_strcasecmp(&route->scheme, "sip"))
+		return ENOSYS;
+
+	req = mem_zalloc(sizeof(*req), destructor);
+	if (!req)
+		return ENOMEM;
+
+	list_append(&sip->reql, &req->le, req);
+
+	err = str_ldup(&req->met, met, metl);
+	if (err)
+		goto out;
+
+	err = str_ldup(&req->uri, uri, uril);
+	if (err)
+		goto out;
+
+	if (msg_param_decode(&route->params, "maddr", &pl))
+		pl = route->host;
+
+	err = pl_strdup(&req->host, &pl);
+	if (err)
+		goto out;
+
+	req->stateful = stateful;
+	req->sortkey = sortkey;
+	req->mb    = mem_ref(mb);
+	req->sip   = sip;
+	req->sendh = sendh;
+	req->resph = resph;
+	req->arg   = arg;
+
+	if (!msg_param_decode(&route->params, "transport", &pl)) {
+
+		if (!pl_strcasecmp(&pl, "udp"))
+			req->tp = SIP_TRANSP_UDP;
+		else if (!pl_strcasecmp(&pl, "tcp"))
+			req->tp = SIP_TRANSP_TCP;
+		else if (!pl_strcasecmp(&pl, "tls"))
+			req->tp = SIP_TRANSP_TLS;
+		else {
+			err = EPROTONOSUPPORT;
+			goto out;
+		}
+
+		if (!sip_transp_supported(sip, req->tp, AF_UNSPEC)) {
+			err = EPROTONOSUPPORT;
+			goto out;
+		}
+
+		req->tp_selected = true;
+	}
+	else {
+		req->tp = SIP_TRANSP_NONE;
+		if (!transp_next(sip, &req->tp)) {
+			err = EPROTONOSUPPORT;
+			goto out;
+		}
+
+		req->tp_selected = false;
+	}
+
+	if (!sa_set_str(&dst, req->host,
+			sip_transp_port(req->tp, route->port))) {
+
+		err = request(req, req->tp, &dst);
+		if (!req->stateful) {
+			mem_deref(req);
+			return err;
+		}
+	}
+	else if (route->port) {
+
+		req->port = sip_transp_port(req->tp, route->port);
+		err = addr_lookup(req, req->host);
+	}
+	else if (req->tp_selected) {
+
+		err = srv_lookup(req, req->host);
+	}
+	else {
+	        err = dnsc_query(&req->dnsq, sip->dnsc, req->host,
+				 DNS_TYPE_NAPTR, DNS_CLASS_IN, true,
+				 naptr_handler, req);
+	}
+
+ out:
+	if (err)
+		mem_deref(req);
+	else if (reqp) {
+		req->reqp = reqp;
+		*reqp = req;
+	}
+
+	return err;
+}
+
+
+/**
+ * Send a SIP request with formatted arguments
+ *
+ * @param reqp     Pointer to allocated SIP request object
+ * @param sip      SIP Stack
+ * @param stateful Stateful client transaction
+ * @param met      Null-terminated SIP Method string
+ * @param uri      Null-terminated Request URI string
+ * @param route    Next hop route URI (optional)
+ * @param auth     SIP authentication state
+ * @param sendh    Send handler
+ * @param resph    Response handler
+ * @param arg      Handler argument
+ * @param fmt      Formatted SIP headers and body
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_requestf(struct sip_request **reqp, struct sip *sip, bool stateful,
+		 const char *met, const char *uri, const struct uri *route,
+		 struct sip_auth *auth, sip_send_h *sendh, sip_resp_h *resph,
+		 void *arg, const char *fmt, ...)
+{
+	struct uri lroute;
+	struct mbuf *mb;
+	va_list ap;
+	int err;
+
+	if (!sip || !met || !uri || !fmt)
+		return EINVAL;
+
+	if (!route) {
+		struct pl uripl;
+
+		pl_set_str(&uripl, uri);
+
+		err = uri_decode(&lroute, &uripl);
+		if (err)
+			return err;
+
+		route = &lroute;
+	}
+
+	mb = mbuf_alloc(2048);
+	if (!mb)
+		return ENOMEM;
+
+	err = mbuf_write_str(mb, "Max-Forwards: 70\r\n");
+
+	if (auth)
+		err |= sip_auth_encode(mb, auth, met, uri);
+
+	if (err)
+		goto out;
+
+	va_start(ap, fmt);
+	err = mbuf_vprintf(mb, fmt, ap);
+	va_end(ap);
+
+	if (err)
+		goto out;
+
+	mb->pos = 0;
+
+	err = sip_request(reqp, sip, stateful, met, -1, uri, -1, route, mb,
+			  (size_t)arg, sendh, resph, arg);
+	if (err)
+		goto out;
+
+ out:
+	mem_deref(mb);
+
+	return err;
+}
+
+
+/**
+ * Send a SIP dialog request with formatted arguments
+ *
+ * @param reqp     Pointer to allocated SIP request object
+ * @param sip      SIP Stack
+ * @param stateful Stateful client transaction
+ * @param met      Null-terminated SIP Method string
+ * @param dlg      SIP Dialog state
+ * @param cseq     CSeq number
+ * @param auth     SIP authentication state
+ * @param sendh    Send handler
+ * @param resph    Response handler
+ * @param arg      Handler argument
+ * @param fmt      Formatted SIP headers and body
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_drequestf(struct sip_request **reqp, struct sip *sip, bool stateful,
+		  const char *met, struct sip_dialog *dlg, uint32_t cseq,
+		  struct sip_auth *auth, sip_send_h *sendh, sip_resp_h *resph,
+		  void *arg, const char *fmt, ...)
+{
+	struct mbuf *mb;
+	va_list ap;
+	int err;
+
+	if (!sip || !met || !dlg || !fmt)
+		return EINVAL;
+
+	mb = mbuf_alloc(2048);
+	if (!mb)
+		return ENOMEM;
+
+	err = mbuf_write_str(mb, "Max-Forwards: 70\r\n");
+
+	if (auth)
+		err |= sip_auth_encode(mb, auth, met, sip_dialog_uri(dlg));
+
+	err |= sip_dialog_encode(mb, dlg, cseq, met);
+
+	if (sip->software)
+		err |= mbuf_printf(mb, "User-Agent: %s\r\n", sip->software);
+
+	if (err)
+		goto out;
+
+	va_start(ap, fmt);
+	err = mbuf_vprintf(mb, fmt, ap);
+	va_end(ap);
+
+	if (err)
+		goto out;
+
+	mb->pos = 0;
+
+	err = sip_request(reqp, sip, stateful, met, -1, sip_dialog_uri(dlg),
+			  -1, sip_dialog_route(dlg), mb, sip_dialog_hash(dlg),
+			  sendh, resph, arg);
+	if (err)
+		goto out;
+
+ out:
+	mem_deref(mb);
+
+	return err;
+}
+
+
+/**
+ * Cancel a pending SIP Request
+ *
+ * @param req SIP Request
+ */
+void sip_request_cancel(struct sip_request *req)
+{
+	if (!req || req->canceled)
+		return;
+
+	req->canceled = true;
+
+	if (!req->provrecv)
+		return;
+
+	(void)sip_ctrans_cancel(req->ct);
+}
+
+
+void sip_request_close(struct sip *sip)
+{
+	if (!sip)
+		return;
+
+	list_apply(&sip->reql, true, close_handler, NULL);
+}
+
+
+/**
+ * Check if a SIP request loops
+ *
+ * @param ls    Loop state
+ * @param scode Status code from SIP response
+ *
+ * @return True if loops, otherwise false
+ */
+bool sip_request_loops(struct sip_loopstate *ls, uint16_t scode)
+{
+	bool loop = false;
+
+	if (!ls)
+		return false;
+
+	if (scode < 200) {
+		return false;
+	}
+	else if (scode < 300) {
+		ls->failc = 0;
+	}
+	else if (scode < 400) {
+		loop = (++ls->failc >= 16);
+	}
+	else {
+		switch (scode) {
+
+		default:
+			if (ls->last_scode == scode)
+				loop = true;
+			/*@fallthrough@*/
+		case 401:
+		case 407:
+		case 491:
+			if (++ls->failc >= 16)
+				loop = true;
+			break;
+		}
+	}
+
+	ls->last_scode = scode;
+
+	return loop;
+}
+
+
+/**
+ * Reset the loop state
+ *
+ * @param ls Loop state
+ */
+void sip_loopstate_reset(struct sip_loopstate *ls)
+{
+	if (!ls)
+		return;
+
+	ls->last_scode = 0;
+	ls->failc = 0;
+}
diff --git a/src/sip/sip.c b/src/sip/sip.c
new file mode 100644
index 0000000..651628c
--- /dev/null
+++ b/src/sip/sip.c
@@ -0,0 +1,238 @@
+/**
+ * @file sip.c  SIP Core
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_sys.h>
+#include <re_tmr.h>
+#include <re_udp.h>
+#include <re_stun.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+static void destructor(void *arg)
+{
+	struct sip *sip = arg;
+
+	if (sip->closing) {
+		sip->closing = false;
+		mem_ref(sip);
+		if (sip->exith)
+			sip->exith(sip->arg);
+		return;
+	}
+
+	sip_request_close(sip);
+	sip_request_close(sip);
+
+	hash_flush(sip->ht_ctrans);
+	mem_deref(sip->ht_ctrans);
+
+	hash_flush(sip->ht_strans);
+	hash_clear(sip->ht_strans_mrg);
+	mem_deref(sip->ht_strans);
+	mem_deref(sip->ht_strans_mrg);
+
+	hash_flush(sip->ht_conn);
+	mem_deref(sip->ht_conn);
+
+	hash_flush(sip->ht_udpconn);
+	mem_deref(sip->ht_udpconn);
+
+	list_flush(&sip->transpl);
+	list_flush(&sip->lsnrl);
+
+	mem_deref(sip->software);
+	mem_deref(sip->dnsc);
+	mem_deref(sip->stun);
+}
+
+
+static void lsnr_destructor(void *arg)
+{
+	struct sip_lsnr *lsnr = arg;
+
+	if (lsnr->lsnrp)
+		*lsnr->lsnrp = NULL;
+
+	list_unlink(&lsnr->le);
+}
+
+
+/**
+ * Allocate a SIP stack instance
+ *
+ * @param sipp     Pointer to allocated SIP stack
+ * @param dnsc     DNS Client (optional)
+ * @param ctsz     Size of client transactions hashtable (power of 2)
+ * @param stsz     Size of server transactions hashtable (power of 2)
+ * @param tcsz     Size of SIP transport hashtable (power of 2)
+ * @param software Software identifier
+ * @param exith    SIP-stack exit handler
+ * @param arg      Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_alloc(struct sip **sipp, struct dnsc *dnsc, uint32_t ctsz,
+	      uint32_t stsz, uint32_t tcsz, const char *software,
+	      sip_exit_h *exith, void *arg)
+{
+	struct sip *sip;
+	int err;
+
+	if (!sipp)
+		return EINVAL;
+
+	sip = mem_zalloc(sizeof(*sip), destructor);
+	if (!sip)
+		return ENOMEM;
+
+	err = sip_transp_init(sip, tcsz);
+	if (err)
+		goto out;
+
+	err = sip_ctrans_init(sip, ctsz);
+	if (err)
+		goto out;
+
+	err = sip_strans_init(sip, stsz);
+	if (err)
+		goto out;
+
+	err = hash_alloc(&sip->ht_udpconn, tcsz);
+	if (err)
+		goto out;
+
+	err = stun_alloc(&sip->stun, NULL, NULL, NULL);
+	if (err)
+		goto out;
+
+	if (software) {
+		err = str_dup(&sip->software, software);
+		if (err)
+			goto out;
+	}
+
+	sip->dnsc  = mem_ref(dnsc);
+	sip->exith = exith;
+	sip->arg   = arg;
+
+ out:
+	if (err)
+		mem_deref(sip);
+	else
+		*sipp = sip;
+
+	return err;
+}
+
+
+/**
+ * Close the SIP stack instance
+ *
+ * @param sip   SIP stack instance
+ * @param force Don't wait for transactions to complete
+ */
+void sip_close(struct sip *sip, bool force)
+{
+	if (!sip)
+		return;
+
+	if (force) {
+		sip_request_close(sip);
+		sip_request_close(sip);
+	}
+	else if (!sip->closing) {
+		sip->closing = true;
+		mem_deref(sip);
+	}
+}
+
+
+/**
+ * Send a SIP message
+ *
+ * @param sip  SIP stack instance
+ * @param sock Optional socket to send from
+ * @param tp   SIP transport
+ * @param dst  Destination network address
+ * @param mb   Buffer containing SIP message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_send(struct sip *sip, void *sock, enum sip_transp tp,
+	     const struct sa *dst, struct mbuf *mb)
+{
+	return sip_transp_send(NULL, sip, sock, tp, dst, mb, NULL, NULL);
+}
+
+
+/**
+ * Listen for incoming SIP Requests and SIP Responses
+ *
+ * @param lsnrp Pointer to allocated listener
+ * @param sip   SIP stack instance
+ * @param req   True for Request, false for Response
+ * @param msgh  SIP message handler
+ * @param arg   Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_listen(struct sip_lsnr **lsnrp, struct sip *sip, bool req,
+	       sip_msg_h *msgh, void *arg)
+{
+	struct sip_lsnr *lsnr;
+
+	if (!sip || !msgh)
+		return EINVAL;
+
+	lsnr = mem_zalloc(sizeof(*lsnr), lsnr_destructor);
+	if (!lsnr)
+		return ENOMEM;
+
+	list_append(&sip->lsnrl, &lsnr->le, lsnr);
+
+	lsnr->msgh = msgh;
+	lsnr->arg = arg;
+	lsnr->req = req;
+
+	if (lsnrp) {
+		lsnr->lsnrp = lsnrp;
+		*lsnrp = lsnr;
+	}
+
+	return 0;
+}
+
+
+/**
+ * Print debug information about the SIP stack
+ *
+ * @param pf  Print function for debug output
+ * @param sip SIP stack instance
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_debug(struct re_printf *pf, const struct sip *sip)
+{
+	int err;
+
+	if (!sip)
+		return 0;
+
+	err  = sip_transp_debug(pf, sip);
+	err |= sip_ctrans_debug(pf, sip);
+	err |= sip_strans_debug(pf, sip);
+
+	return err;
+}
diff --git a/src/sip/sip.h b/src/sip/sip.h
new file mode 100644
index 0000000..912c69b
--- /dev/null
+++ b/src/sip/sip.h
@@ -0,0 +1,101 @@
+/**
+ * @file sip.h  SIP Private Interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+struct sip {
+	struct list transpl;
+	struct list lsnrl;
+	struct list reql;
+	struct hash *ht_ctrans;
+	struct hash *ht_strans;
+	struct hash *ht_strans_mrg;
+	struct hash *ht_conn;
+	struct hash *ht_udpconn;
+	struct dnsc *dnsc;
+	struct stun *stun;
+	char *software;
+	sip_exit_h *exith;
+	void *arg;
+	bool closing;
+};
+
+
+struct sip_lsnr {
+	struct le le;
+	struct sip_lsnr **lsnrp;
+	sip_msg_h *msgh;
+	void *arg;
+	bool req;
+};
+
+
+struct sip_keepalive {
+	struct le le;
+	struct sip_keepalive **kap;
+	sip_keepalive_h *kah;
+	void *arg;
+};
+
+
+/* request */
+void sip_request_close(struct sip *sip);
+
+
+/* ctrans */
+struct sip_ctrans;
+
+int  sip_ctrans_request(struct sip_ctrans **ctp, struct sip *sip,
+			enum sip_transp tp, const struct sa *dst, char *met,
+			char *branch, struct mbuf *mb, sip_resp_h *resph,
+			void *arg);
+int  sip_ctrans_cancel(struct sip_ctrans *ct);
+int  sip_ctrans_init(struct sip *sip, uint32_t sz);
+int  sip_ctrans_debug(struct re_printf *pf, const struct sip *sip);
+
+
+/* strans */
+int  sip_strans_init(struct sip *sip, uint32_t sz);
+int  sip_strans_debug(struct re_printf *pf, const struct sip *sip);
+
+
+/* transp */
+struct sip_connqent;
+
+typedef void(sip_transp_h)(int err, void *arg);
+
+int  sip_transp_init(struct sip *sip, uint32_t sz);
+int  sip_transp_send(struct sip_connqent **qentp, struct sip *sip, void *sock,
+		     enum sip_transp tp, const struct sa *dst, struct mbuf *mb,
+		     sip_transp_h *transph, void *arg);
+bool sip_transp_supported(struct sip *sip, enum sip_transp tp, int af);
+const char *sip_transp_srvid(enum sip_transp tp);
+bool sip_transp_reliable(enum sip_transp tp);
+int  sip_transp_debug(struct re_printf *pf, const struct sip *sip);
+
+
+/* auth */
+int  sip_auth_encode(struct mbuf *mb, struct sip_auth *auth, const char *met,
+		     const char *uri);
+
+
+/* dialog */
+int  sip_dialog_encode(struct mbuf *mb, struct sip_dialog *dlg, uint32_t cseq,
+		       const char *met);
+const char *sip_dialog_uri(const struct sip_dialog *dlg);
+const struct uri *sip_dialog_route(const struct sip_dialog *dlg);
+uint32_t sip_dialog_hash(const struct sip_dialog *dlg);
+
+
+/* keepalive */
+struct sip_conn;
+
+void sip_keepalive_signal(struct list *kal, int err);
+uint64_t sip_keepalive_wait(uint32_t interval);
+int  sip_keepalive_tcp(struct sip_keepalive *ka, struct sip_conn *conn,
+		       uint32_t interval);
+int  sip_keepalive_udp(struct sip_keepalive *ka, struct sip *sip,
+		       struct udp_sock *us, const struct sa *paddr,
+		       uint32_t interval);
diff --git a/src/sip/strans.c b/src/sip/strans.c
new file mode 100644
index 0000000..13a875c
--- /dev/null
+++ b/src/sip/strans.c
@@ -0,0 +1,455 @@
+/**
+ * @file strans.c  SIP Server Transaction
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_sys.h>
+#include <re_tmr.h>
+#include <re_udp.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+enum state {
+	TRYING,
+	PROCEEDING,
+	ACCEPTED,
+	COMPLETED,
+	CONFIRMED,
+};
+
+
+struct sip_strans {
+	struct le he;
+	struct le he_mrg;
+	struct tmr tmr;
+	struct tmr tmrg;
+	struct sa dst;
+	struct sip *sip;
+	struct sip_msg *msg;
+	struct mbuf *mb;
+	sip_cancel_h *cancelh;
+	void *arg;
+	enum state state;
+	uint32_t txc;
+	bool invite;
+};
+
+
+static void destructor(void *arg)
+{
+	struct sip_strans *st = arg;
+
+	hash_unlink(&st->he);
+	hash_unlink(&st->he_mrg);
+	tmr_cancel(&st->tmr);
+	tmr_cancel(&st->tmrg);
+	mem_deref(st->msg);
+	mem_deref(st->mb);
+}
+
+
+static bool strans_cmp(const struct sip_msg *msg1, const struct sip_msg *msg2)
+{
+	if (pl_cmp(&msg1->via.branch, &msg2->via.branch))
+		return false;
+
+	if (pl_cmp(&msg1->via.sentby, &msg2->via.sentby))
+		return false;
+
+	return true;
+}
+
+
+static bool cmp_handler(struct le *le, void *arg)
+{
+	struct sip_strans *st = le->data;
+	const struct sip_msg *msg = arg;
+
+	if (!strans_cmp(st->msg, msg))
+		return false;
+
+	if (pl_cmp(&st->msg->cseq.met, &msg->cseq.met))
+		return false;
+
+	return true;
+}
+
+
+static bool cmp_ack_handler(struct le *le, void *arg)
+{
+	struct sip_strans *st = le->data;
+	const struct sip_msg *msg = arg;
+
+	if (!strans_cmp(st->msg, msg))
+		return false;
+
+	if (pl_strcmp(&st->msg->cseq.met, "INVITE"))
+		return false;
+
+	return true;
+}
+
+
+static bool cmp_cancel_handler(struct le *le, void *arg)
+{
+	struct sip_strans *st = le->data;
+	const struct sip_msg *msg = arg;
+
+	if (!strans_cmp(st->msg, msg))
+		return false;
+
+	if (!pl_strcmp(&st->msg->cseq.met, "CANCEL"))
+		return false;
+
+	return true;
+}
+
+
+static bool cmp_merge_handler(struct le *le, void *arg)
+{
+	struct sip_strans *st = le->data;
+	const struct sip_msg *msg = arg;
+
+	if (pl_cmp(&st->msg->cseq.met, &msg->cseq.met))
+		return false;
+
+	if (st->msg->cseq.num != msg->cseq.num)
+		return false;
+
+	if (pl_cmp(&st->msg->callid, &msg->callid))
+		return false;
+
+	if (pl_cmp(&st->msg->from.tag, &msg->from.tag))
+		return false;
+
+	if (pl_cmp(&st->msg->ruri, &msg->ruri))
+		return false;
+
+	return true;
+}
+
+
+static void dummy_handler(void *arg)
+{
+	(void)arg;
+}
+
+
+static void tmr_handler(void *arg)
+{
+	struct sip_strans *st = arg;
+
+	mem_deref(st);
+}
+
+
+static void retransmit_handler(void *arg)
+{
+	struct sip_strans *st = arg;
+
+	(void)sip_send(st->sip, st->msg->sock, st->msg->tp, &st->dst,
+		       st->mb);
+
+	st->txc++;
+	tmr_start(&st->tmrg, MIN(SIP_T1<<st->txc, SIP_T2), retransmit_handler,
+		  st);
+}
+
+
+static bool ack_handler(struct sip *sip, const struct sip_msg *msg)
+{
+	struct sip_strans *st;
+
+	st = list_ledata(hash_lookup(sip->ht_strans,
+				     hash_joaat_pl(&msg->via.branch),
+				     cmp_ack_handler, (void *)msg));
+	if (!st)
+		return false;
+
+	switch (st->state) {
+
+	case ACCEPTED:
+		/* make sure ACKs for 2xx are passed to TU */
+		return false;
+
+	case COMPLETED:
+		if (sip_transp_reliable(st->msg->tp)) {
+			mem_deref(st);
+			break;
+		}
+
+		tmr_start(&st->tmr, SIP_T4, tmr_handler, st);
+		tmr_cancel(&st->tmrg);
+		st->state = CONFIRMED;
+		break;
+
+	default:
+		break;
+	}
+
+	return true;
+}
+
+
+static bool cancel_handler(struct sip *sip, const struct sip_msg *msg)
+{
+	struct sip_strans *st;
+
+	st = list_ledata(hash_lookup(sip->ht_strans,
+				     hash_joaat_pl(&msg->via.branch),
+				     cmp_cancel_handler, (void *)msg));
+	if (!st)
+		return false;
+
+	((struct sip_msg *)msg)->tag = st->msg->tag;
+
+	(void)sip_reply(sip, msg, 200, "OK");
+
+	switch (st->state) {
+
+	case TRYING:
+	case PROCEEDING:
+		st->cancelh(st->arg);
+		break;
+
+	default:
+		break;
+	}
+
+	return true;
+}
+
+
+static bool request_handler(const struct sip_msg *msg, void *arg)
+{
+	struct sip_strans *st;
+	struct sip *sip = arg;
+
+	if (!pl_strcmp(&msg->met, "ACK"))
+		return ack_handler(sip, msg);
+
+	st = list_ledata(hash_lookup(sip->ht_strans,
+				     hash_joaat_pl(&msg->via.branch),
+				     cmp_handler, (void *)msg));
+	if (st) {
+		switch (st->state) {
+
+		case PROCEEDING:
+		case COMPLETED:
+			(void)sip_send(st->sip, st->msg->sock, st->msg->tp,
+				       &st->dst, st->mb);
+			break;
+
+		default:
+			break;
+		}
+
+		return true;
+	}
+	else if (!pl_isset(&msg->to.tag)) {
+
+		st = list_ledata(hash_lookup(sip->ht_strans_mrg,
+					     hash_joaat_pl(&msg->callid),
+					     cmp_merge_handler, (void *)msg));
+		if (st) {
+			(void)sip_reply(sip, msg, 482, "Loop Detected");
+			return true;
+		}
+	}
+
+	if (!pl_strcmp(&msg->met, "CANCEL"))
+		return cancel_handler(sip, msg);
+
+	return false;
+}
+
+
+/**
+ * Allocate a SIP Server Transaction
+ *
+ * @param stp     Pointer to allocated SIP Server Transaction
+ * @param sip     SIP Stack instance
+ * @param msg     Incoming SIP message
+ * @param cancelh Cancel handler
+ * @param arg     Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_strans_alloc(struct sip_strans **stp, struct sip *sip,
+		     const struct sip_msg *msg, sip_cancel_h *cancelh,
+		     void *arg)
+{
+	struct sip_strans *st;
+
+	if (!stp || !sip || !msg)
+		return EINVAL;
+
+	st = mem_zalloc(sizeof(*st), destructor);
+	if (!st)
+		return ENOMEM;
+
+	hash_append(sip->ht_strans, hash_joaat_pl(&msg->via.branch),
+		    &st->he, st);
+
+	hash_append(sip->ht_strans_mrg, hash_joaat_pl(&msg->callid),
+		    &st->he_mrg, st);
+
+	st->invite  = !pl_strcmp(&msg->met, "INVITE");
+	st->msg     = mem_ref((void *)msg);
+	st->state   = TRYING;
+	st->cancelh = cancelh ? cancelh : dummy_handler;
+	st->arg     = arg;
+	st->sip     = sip;
+
+	*stp = st;
+
+	return 0;
+}
+
+
+/**
+ * Reply using a SIP Server Transaction
+ *
+ * @param stp   Pointer to allocated SIP Server Transaction
+ * @param sip   SIP Stack instance
+ * @param msg   Incoming SIP message
+ * @param dst   Destination network address
+ * @param scode Response status code
+ * @param mb    Buffer containing SIP response
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_strans_reply(struct sip_strans **stp, struct sip *sip,
+		     const struct sip_msg *msg, const struct sa *dst,
+		     uint16_t scode, struct mbuf *mb)
+{
+	struct sip_strans *st = NULL;
+	int err;
+
+	if (!sip || !mb || !dst || (scode < 200 && !stp))
+		return EINVAL;
+
+	if (stp)
+		st = *stp;
+
+	if (!st) {
+		err = sip_strans_alloc(&st, sip, msg, NULL, NULL);
+		if (err)
+			return err;
+	}
+
+	mem_deref(st->mb);
+	st->mb = mem_ref(mb);
+	st->dst = *dst;
+
+	err = sip_send(sip, st->msg->sock, st->msg->tp, dst, mb);
+
+	if (stp)
+		*stp = (err || scode >= 200) ? NULL : st;
+
+	if (err) {
+		mem_deref(st);
+		return err;
+	}
+
+	if (st->invite) {
+		if (scode < 200) {
+			st->state = PROCEEDING;
+		}
+		else if (scode < 300) {
+			tmr_start(&st->tmr, 64 * SIP_T1, tmr_handler, st);
+			st->state = ACCEPTED;
+		}
+		else {
+			tmr_start(&st->tmr, 64 * SIP_T1, tmr_handler, st);
+			st->state = COMPLETED;
+
+			if (!sip_transp_reliable(st->msg->tp))
+				tmr_start(&st->tmrg, SIP_T1,
+					  retransmit_handler, st);
+		}
+	}
+	else {
+		if (scode < 200) {
+			st->state = PROCEEDING;
+		}
+		else {
+			if (!sip_transp_reliable(st->msg->tp)) {
+				tmr_start(&st->tmr, 64 * SIP_T1, tmr_handler,
+					  st);
+				st->state = COMPLETED;
+			}
+			else {
+				mem_deref(st);
+			}
+		}
+	}
+
+	return 0;
+}
+
+
+int sip_strans_init(struct sip *sip, uint32_t sz)
+{
+	int err;
+
+	err = sip_listen(NULL, sip, true, request_handler, sip);
+	if (err)
+		return err;
+
+	err = hash_alloc(&sip->ht_strans_mrg, sz);
+	if (err)
+		return err;
+
+	return hash_alloc(&sip->ht_strans, sz);
+}
+
+
+static const char *statename(enum state state)
+{
+	switch (state) {
+
+	case TRYING:     return "TRYING";
+	case PROCEEDING: return "PROCEEDING";
+	case ACCEPTED:   return "ACCEPTED";
+	case COMPLETED:  return "COMPLETED";
+	case CONFIRMED:  return "CONFIRMED";
+	default:         return "???";
+	}
+}
+
+
+static bool debug_handler(struct le *le, void *arg)
+{
+	struct sip_strans *st = le->data;
+	struct re_printf *pf = arg;
+
+	(void)re_hprintf(pf, "  %-10r %-10s %2llus (%r)\n",
+			 &st->msg->met,
+			 statename(st->state),
+			 tmr_get_expire(&st->tmr)/1000,
+			 &st->msg->via.branch);
+
+	return false;
+}
+
+
+int sip_strans_debug(struct re_printf *pf, const struct sip *sip)
+{
+	int err;
+
+	err = re_hprintf(pf, "server transactions:\n");
+	hash_apply(sip->ht_strans, debug_handler, pf);
+
+	return err;
+}
diff --git a/src/sip/transp.c b/src/sip/transp.c
new file mode 100644
index 0000000..aed33bb
--- /dev/null
+++ b/src/sip/transp.c
@@ -0,0 +1,985 @@
+/**
+ * @file sip/transp.c  SIP Transport
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_sys.h>
+#include <re_tmr.h>
+#include <re_udp.h>
+#include <re_stun.h>
+#include <re_srtp.h>
+#include <re_tcp.h>
+#include <re_tls.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+enum {
+	TCP_ACCEPT_TIMEOUT    = 32,
+	TCP_IDLE_TIMEOUT      = 900,
+	TCP_KEEPALIVE_TIMEOUT = 10,
+	TCP_KEEPALIVE_INTVAL  = 120,
+	TCP_BUFSIZE_MAX       = 65536,
+};
+
+
+struct sip_transport {
+	struct le le;
+	struct sa laddr;
+	struct sip *sip;
+	struct tls *tls;
+	void *sock;
+	enum sip_transp tp;
+};
+
+
+struct sip_conn {
+	struct le he;
+	struct list ql;
+	struct list kal;
+	struct tmr tmr;
+	struct tmr tmr_ka;
+	struct sa laddr;
+	struct sa paddr;
+	struct tls_conn *sc;
+	struct tcp_conn *tc;
+	struct mbuf *mb;
+	struct sip *sip;
+	uint32_t ka_interval;
+	bool established;
+};
+
+
+struct sip_connqent {
+	struct le le;
+	struct mbuf *mb;
+	struct sip_connqent **qentp;
+	sip_transp_h *transph;
+	void *arg;
+};
+
+
+static uint8_t crlfcrlf[4] = {0x0d, 0x0a, 0x0d, 0x0a};
+
+
+static void internal_transport_handler(int err, void *arg)
+{
+	(void)err;
+	(void)arg;
+}
+
+
+static void transp_destructor(void *arg)
+{
+	struct sip_transport *transp = arg;
+
+	if (transp->tp == SIP_TRANSP_UDP)
+		udp_handler_set(transp->sock, NULL, NULL);
+
+	list_unlink(&transp->le);
+	mem_deref(transp->sock);
+	mem_deref(transp->tls);
+}
+
+
+static void conn_destructor(void *arg)
+{
+	struct sip_conn *conn = arg;
+
+	tmr_cancel(&conn->tmr_ka);
+	tmr_cancel(&conn->tmr);
+	list_flush(&conn->kal);
+	list_flush(&conn->ql);
+	hash_unlink(&conn->he);
+	mem_deref(conn->sc);
+	mem_deref(conn->tc);
+	mem_deref(conn->mb);
+}
+
+
+static void qent_destructor(void *arg)
+{
+	struct sip_connqent *qent = arg;
+
+	if (qent->qentp)
+		*qent->qentp = NULL;
+
+	list_unlink(&qent->le);
+	mem_deref(qent->mb);
+}
+
+
+static const struct sip_transport *transp_find(struct sip *sip,
+					       enum sip_transp tp,
+					       int af, const struct sa *dst)
+{
+	struct le *le;
+	(void)dst;
+
+	for (le = sip->transpl.head; le; le = le->next) {
+
+		const struct sip_transport *transp = le->data;
+
+		if (transp->tp != tp)
+			continue;
+
+		if (af != AF_UNSPEC && sa_af(&transp->laddr) != af)
+			continue;
+
+		return transp;
+	}
+
+	return NULL;
+}
+
+
+static struct sip_conn *conn_find(struct sip *sip, const struct sa *paddr,
+				  bool secure)
+{
+	struct le *le;
+
+	le = list_head(hash_list(sip->ht_conn, sa_hash(paddr, SA_ALL)));
+
+	for (; le; le = le->next) {
+
+		struct sip_conn *conn = le->data;
+
+		if (!secure != (conn->sc == NULL))
+			continue;
+
+		if (!sa_cmp(&conn->paddr, paddr, SA_ALL))
+			continue;
+
+		return conn;
+	}
+
+	return NULL;
+}
+
+
+static void conn_close(struct sip_conn *conn, int err)
+{
+	struct le *le;
+
+	conn->sc = mem_deref(conn->sc);
+	conn->tc = mem_deref(conn->tc);
+	tmr_cancel(&conn->tmr_ka);
+	tmr_cancel(&conn->tmr);
+	hash_unlink(&conn->he);
+
+	le = list_head(&conn->ql);
+
+	while (le) {
+
+		struct sip_connqent *qent = le->data;
+		le = le->next;
+
+		if (qent->qentp) {
+			*qent->qentp = NULL;
+			qent->qentp = NULL;
+		}
+
+		qent->transph(err, qent->arg);
+		list_unlink(&qent->le);
+		mem_deref(qent);
+	}
+
+	sip_keepalive_signal(&conn->kal, err);
+}
+
+
+static void conn_tmr_handler(void *arg)
+{
+	struct sip_conn *conn = arg;
+
+	conn_close(conn, ETIMEDOUT);
+	mem_deref(conn);
+}
+
+
+static void conn_keepalive_handler(void *arg)
+{
+	struct sip_conn *conn = arg;
+	struct mbuf mb;
+	int err;
+
+	mb.buf  = crlfcrlf;
+	mb.size = sizeof(crlfcrlf);
+	mb.pos  = 0;
+	mb.end  = 4;
+
+	err = tcp_send(conn->tc, &mb);
+	if (err) {
+		conn_close(conn, err);
+		mem_deref(conn);
+		return;
+	}
+
+	tmr_start(&conn->tmr, TCP_KEEPALIVE_TIMEOUT * 1000,
+		  conn_tmr_handler, conn);
+	tmr_start(&conn->tmr_ka, sip_keepalive_wait(conn->ka_interval),
+		  conn_keepalive_handler, conn);
+}
+
+
+static void sip_recv(struct sip *sip, const struct sip_msg *msg)
+{
+	struct le *le = sip->lsnrl.head;
+
+	while (le) {
+		struct sip_lsnr *lsnr = le->data;
+
+		le = le->next;
+
+		if (msg->req != lsnr->req)
+			continue;
+
+		if (lsnr->msgh(msg, lsnr->arg))
+			return;
+	}
+
+	if (msg->req) {
+		(void)re_fprintf(stderr, "unhandeled request from %J: %r %r\n",
+				 &msg->src, &msg->met, &msg->ruri);
+
+		if (!pl_strcmp(&msg->met, "CANCEL"))
+			(void)sip_reply(sip, msg,
+					481, "Transaction Does Not Exist");
+		else
+			(void)sip_reply(sip, msg,
+					501, "Not Implemented");
+	}
+	else {
+		(void)re_fprintf(stderr, "unhandeled response from %J:"
+				 " %u %r (%r)\n", &msg->src,
+				 msg->scode, &msg->reason, &msg->cseq.met);
+	}
+}
+
+
+static void udp_recv_handler(const struct sa *src, struct mbuf *mb, void *arg)
+{
+	struct sip_transport *transp = arg;
+	struct stun_unknown_attr ua;
+	struct stun_msg *stun_msg;
+	struct sip_msg *msg;
+	int err;
+
+	if (mb->end <= 4)
+		return;
+
+	if (!stun_msg_decode(&stun_msg, mb, &ua)) {
+
+		if (stun_msg_method(stun_msg) == STUN_METHOD_BINDING) {
+
+			switch (stun_msg_class(stun_msg)) {
+
+			case STUN_CLASS_REQUEST:
+				(void)stun_reply(IPPROTO_UDP, transp->sock,
+						 src, 0, stun_msg,
+						 NULL, 0, false, 2,
+						 STUN_ATTR_XOR_MAPPED_ADDR,
+						 src,
+						 STUN_ATTR_SOFTWARE,
+						 transp->sip->software);
+				break;
+
+			default:
+				(void)stun_ctrans_recv(transp->sip->stun,
+						       stun_msg, &ua);
+				break;
+			}
+		}
+
+		mem_deref(stun_msg);
+
+		return;
+	}
+
+	err = sip_msg_decode(&msg, mb);
+	if (err) {
+		(void)re_fprintf(stderr, "sip: msg decode err: %m\n", err);
+		return;
+	}
+
+	msg->sock = mem_ref(transp->sock);
+	msg->src = *src;
+	msg->dst = transp->laddr;
+	msg->tp = SIP_TRANSP_UDP;
+
+	sip_recv(transp->sip, msg);
+
+	mem_deref(msg);
+}
+
+
+static void tcp_recv_handler(struct mbuf *mb, void *arg)
+{
+	struct sip_conn *conn = arg;
+	size_t pos;
+	int err = 0;
+
+	if (conn->mb) {
+		pos = conn->mb->pos;
+
+		conn->mb->pos = conn->mb->end;
+
+		err = mbuf_write_mem(conn->mb, mbuf_buf(mb),mbuf_get_left(mb));
+		if (err)
+			goto out;
+
+		conn->mb->pos = pos;
+
+		if (mbuf_get_left(conn->mb) > TCP_BUFSIZE_MAX) {
+			err = EOVERFLOW;
+			goto out;
+		}
+	}
+	else {
+		conn->mb = mem_ref(mb);
+	}
+
+	for (;;) {
+		struct sip_msg *msg;
+		uint32_t clen;
+		size_t end;
+
+		if (mbuf_get_left(conn->mb) < 2)
+			break;
+
+		if (!memcmp(mbuf_buf(conn->mb), "\r\n", 2)) {
+
+			tmr_start(&conn->tmr, TCP_IDLE_TIMEOUT * 1000,
+				  conn_tmr_handler, conn);
+
+			conn->mb->pos += 2;
+
+			if (mbuf_get_left(conn->mb) >= 2 &&
+			    !memcmp(mbuf_buf(conn->mb), "\r\n", 2)) {
+
+				struct mbuf mbr;
+
+				conn->mb->pos += 2;
+
+				mbr.buf  = crlfcrlf;
+				mbr.size = sizeof(crlfcrlf);
+				mbr.pos  = 0;
+				mbr.end  = 2;
+
+				err = tcp_send(conn->tc, &mbr);
+				if (err)
+					break;
+			}
+
+			if (mbuf_get_left(conn->mb))
+				continue;
+
+			conn->mb = mem_deref(conn->mb);
+			break;
+		}
+
+		pos = conn->mb->pos;
+
+		err = sip_msg_decode(&msg, conn->mb);
+		if (err) {
+			if (err == ENODATA)
+				err = 0;
+			break;
+		}
+
+		if (!msg->clen.p) {
+			mem_deref(msg);
+			err = EBADMSG;
+			break;
+		}
+
+		clen = pl_u32(&msg->clen);
+
+		if (mbuf_get_left(conn->mb) < clen) {
+			conn->mb->pos = pos;
+			mem_deref(msg);
+			break;
+		}
+
+		tmr_start(&conn->tmr, TCP_IDLE_TIMEOUT * 1000,
+			  conn_tmr_handler, conn);
+
+		end = conn->mb->end;
+
+		msg->mb->end = msg->mb->pos + clen;
+		msg->sock = mem_ref(conn);
+		msg->src = conn->paddr;
+		msg->dst = conn->laddr;
+		msg->tp = conn->sc ? SIP_TRANSP_TLS : SIP_TRANSP_TCP;
+
+		sip_recv(conn->sip, msg);
+		mem_deref(msg);
+
+		if (end <= conn->mb->end) {
+			conn->mb = mem_deref(conn->mb);
+			break;
+		}
+
+		mb = mbuf_alloc(end - conn->mb->end);
+		if (!mb) {
+			err = ENOMEM;
+			goto out;
+		}
+
+		(void)mbuf_write_mem(mb, &conn->mb->buf[conn->mb->end],
+				     end - conn->mb->end);
+
+		mb->pos = 0;
+
+		mem_deref(conn->mb);
+		conn->mb = mb;
+	}
+
+ out:
+	if (err) {
+		conn_close(conn, err);
+		mem_deref(conn);
+	}
+}
+
+
+static void tcp_estab_handler(void *arg)
+{
+	struct sip_conn *conn = arg;
+	struct le *le;
+	int err;
+
+#ifdef WIN32
+	tcp_conn_local_get(conn->tc, &conn->laddr);
+#endif
+
+	conn->established = true;
+
+	le = list_head(&conn->ql);
+
+	while (le) {
+
+		struct sip_connqent *qent = le->data;
+		le = le->next;
+
+		if (qent->qentp) {
+			*qent->qentp = NULL;
+			qent->qentp = NULL;
+		}
+
+		err = tcp_send(conn->tc, qent->mb);
+		if (err)
+			qent->transph(err, qent->arg);
+
+		list_unlink(&qent->le);
+		mem_deref(qent);
+	}
+}
+
+
+static void tcp_close_handler(int err, void *arg)
+{
+	struct sip_conn *conn = arg;
+
+	conn_close(conn, err ? err : ECONNRESET);
+	mem_deref(conn);
+}
+
+
+static void tcp_connect_handler(const struct sa *paddr, void *arg)
+{
+	struct sip_transport *transp = arg;
+	struct sip_conn *conn;
+	int err;
+
+	conn = mem_zalloc(sizeof(*conn), conn_destructor);
+	if (!conn) {
+		err = ENOMEM;
+		goto out;
+	}
+
+	hash_append(transp->sip->ht_conn, sa_hash(paddr, SA_ALL),
+		    &conn->he, conn);
+
+	conn->paddr = *paddr;
+	conn->sip   = transp->sip;
+
+	err = tcp_accept(&conn->tc, transp->sock, tcp_estab_handler,
+			 tcp_recv_handler, tcp_close_handler, conn);
+	if (err)
+		goto out;
+
+	err = tcp_conn_local_get(conn->tc, &conn->laddr);
+	if (err)
+		goto out;
+
+#ifdef USE_TLS
+	if (transp->tls) {
+		err = tls_start_tcp(&conn->sc, transp->tls, conn->tc, 0);
+		if (err)
+			goto out;
+	}
+#endif
+
+	tmr_start(&conn->tmr, TCP_ACCEPT_TIMEOUT * 1000,
+		  conn_tmr_handler, conn);
+
+ out:
+	if (err) {
+		tcp_reject(transp->sock);
+		mem_deref(conn);
+	}
+}
+
+
+static int conn_send(struct sip_connqent **qentp, struct sip *sip, bool secure,
+		     const struct sa *dst, struct mbuf *mb,
+		     sip_transp_h *transph, void *arg)
+{
+	struct sip_conn *conn, *new_conn = NULL;
+	struct sip_connqent *qent;
+	int err = 0;
+
+	conn = conn_find(sip, dst, secure);
+	if (conn) {
+		if (!conn->established)
+			goto enqueue;
+
+		return tcp_send(conn->tc, mb);
+	}
+
+	new_conn = conn = mem_zalloc(sizeof(*conn), conn_destructor);
+	if (!conn)
+		return ENOMEM;
+
+	hash_append(sip->ht_conn, sa_hash(dst, SA_ALL), &conn->he, conn);
+	conn->paddr = *dst;
+	conn->sip   = sip;
+
+	err = tcp_connect(&conn->tc, dst, tcp_estab_handler, tcp_recv_handler,
+			  tcp_close_handler, conn);
+	if (err)
+		goto out;
+
+	err = tcp_conn_local_get(conn->tc, &conn->laddr);
+	if (err)
+		goto out;
+
+#ifdef USE_TLS
+	if (secure) {
+		const struct sip_transport *transp;
+
+		transp = transp_find(sip, SIP_TRANSP_TLS, sa_af(dst), dst);
+		if (!transp || !transp->tls) {
+			err = EPROTONOSUPPORT;
+			goto out;
+		}
+
+		err = tls_start_tcp(&conn->sc, transp->tls, conn->tc, 0);
+		if (err)
+			goto out;
+	}
+#endif
+
+	tmr_start(&conn->tmr, TCP_IDLE_TIMEOUT * 1000, conn_tmr_handler, conn);
+
+ enqueue:
+	qent = mem_zalloc(sizeof(*qent), qent_destructor);
+	if (!qent) {
+		err = ENOMEM;
+		goto out;
+
+	}
+
+	list_append(&conn->ql, &qent->le, qent);
+	qent->mb = mem_ref(mb);
+	qent->transph = transph ? transph : internal_transport_handler;
+	qent->arg = arg;
+
+	if (qentp) {
+		qent->qentp = qentp;
+		*qentp = qent;
+	}
+
+ out:
+	if (err)
+		mem_deref(new_conn);
+
+	return err;
+}
+
+
+int sip_transp_init(struct sip *sip, uint32_t sz)
+{
+	return hash_alloc(&sip->ht_conn, sz);
+}
+
+
+/**
+ * Add a SIP transport
+ *
+ * @param sip   SIP stack instance
+ * @param tp    SIP Transport
+ * @param laddr Local network address
+ * @param ...   Optional transport parameters such as TLS context
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_transp_add(struct sip *sip, enum sip_transp tp,
+		   const struct sa *laddr, ...)
+{
+	struct sip_transport *transp;
+	struct tls *tls;
+	va_list ap;
+	int err;
+
+	if (!sip || !laddr || !sa_isset(laddr, SA_ADDR))
+		return EINVAL;
+
+	transp = mem_zalloc(sizeof(*transp), transp_destructor);
+	if (!transp)
+		return ENOMEM;
+
+	list_append(&sip->transpl, &transp->le, transp);
+	transp->sip = sip;
+	transp->tp  = tp;
+
+	va_start(ap, laddr);
+
+	switch (tp) {
+
+	case SIP_TRANSP_UDP:
+		err = udp_listen((struct udp_sock **)&transp->sock, laddr,
+				 udp_recv_handler, transp);
+		if (err)
+			break;
+
+		err = udp_local_get(transp->sock, &transp->laddr);
+		break;
+
+	case SIP_TRANSP_TLS:
+		tls = va_arg(ap, struct tls *);
+		if (!tls) {
+			err = EINVAL;
+			break;
+		}
+
+		transp->tls = mem_ref(tls);
+
+		/*@fallthrough@*/
+
+	case SIP_TRANSP_TCP:
+		err = tcp_listen((struct tcp_sock **)&transp->sock, laddr,
+				 tcp_connect_handler, transp);
+		if (err)
+			break;
+
+		err = tcp_sock_local_get(transp->sock, &transp->laddr);
+		break;
+
+	default:
+		err = EPROTONOSUPPORT;
+		break;
+	}
+
+	va_end(ap);
+
+	if (err)
+		mem_deref(transp);
+
+	return err;
+}
+
+
+/**
+ * Flush all transports of a SIP stack instance
+ *
+ * @param sip SIP stack instance
+ */
+void sip_transp_flush(struct sip *sip)
+{
+	if (!sip)
+		return;
+
+	hash_flush(sip->ht_conn);
+	list_flush(&sip->transpl);
+}
+
+
+int sip_transp_send(struct sip_connqent **qentp, struct sip *sip, void *sock,
+		    enum sip_transp tp, const struct sa *dst, struct mbuf *mb,
+		    sip_transp_h *transph, void *arg)
+{
+	const struct sip_transport *transp;
+	struct sip_conn *conn;
+	bool secure = false;
+	int err;
+
+	if (!sip || !dst || !mb)
+		return EINVAL;
+
+	switch (tp) {
+
+	case SIP_TRANSP_UDP:
+		if (!sock) {
+			transp = transp_find(sip, tp, sa_af(dst), dst);
+			if (!transp)
+				return EPROTONOSUPPORT;
+
+			sock = transp->sock;
+		}
+
+		err = udp_send(sock, dst, mb);
+		break;
+
+	case SIP_TRANSP_TLS:
+		secure = true;
+		/*@fallthrough@*/
+
+	case SIP_TRANSP_TCP:
+		conn = sock;
+
+		if (conn && conn->tc)
+			err = tcp_send(conn->tc, mb);
+		else
+			err = conn_send(qentp, sip, secure, dst, mb,
+					transph, arg);
+		break;
+
+	default:
+		err = EPROTONOSUPPORT;
+		break;
+	}
+
+	return err;
+}
+
+
+int sip_transp_laddr(struct sip *sip, struct sa *laddr, enum sip_transp tp,
+		      const struct sa *dst)
+{
+	const struct sip_transport *transp;
+
+	if (!sip || !laddr)
+		return EINVAL;
+
+	transp = transp_find(sip, tp, sa_af(dst), dst);
+	if (!transp)
+		return EPROTONOSUPPORT;
+
+	*laddr = transp->laddr;
+
+	return 0;
+}
+
+
+bool sip_transp_supported(struct sip *sip, enum sip_transp tp, int af)
+{
+	if (!sip)
+		return false;
+
+	return transp_find(sip, tp, af, NULL) != NULL;
+}
+
+
+/**
+ * Check if network address is part of SIP transports
+ *
+ * @param sip   SIP stack instance
+ * @param tp    SIP transport
+ * @param laddr Local network address to check
+ *
+ * @return True if part of SIP transports, otherwise false
+ */
+bool sip_transp_isladdr(const struct sip *sip, enum sip_transp tp,
+			const struct sa *laddr)
+{
+	struct le *le;
+
+	if (!sip || !laddr)
+		return false;
+
+	for (le=sip->transpl.head; le; le=le->next) {
+
+		const struct sip_transport *transp = le->data;
+
+		if (tp != SIP_TRANSP_NONE && transp->tp != tp)
+			continue;
+
+		if (!sa_cmp(&transp->laddr, laddr, SA_ALL))
+			continue;
+
+		return true;
+	}
+
+	return false;
+}
+
+
+/**
+ * Get the name of a given SIP Transport
+ *
+ * @param tp SIP Transport
+ *
+ * @return Name of the corresponding SIP Transport
+ */
+const char *sip_transp_name(enum sip_transp tp)
+{
+	switch (tp) {
+
+	case SIP_TRANSP_UDP: return "UDP";
+	case SIP_TRANSP_TCP: return "TCP";
+	case SIP_TRANSP_TLS: return "TLS";
+	default:             return "???";
+	}
+}
+
+
+const char *sip_transp_srvid(enum sip_transp tp)
+{
+	switch (tp) {
+
+	case SIP_TRANSP_UDP: return "_sip._udp";
+	case SIP_TRANSP_TCP: return "_sip._tcp";
+	case SIP_TRANSP_TLS: return "_sips._tcp";
+	default:             return "???";
+	}
+}
+
+
+/**
+ * Get the transport parameters for a given SIP Transport
+ *
+ * @param tp SIP Transport
+ *
+ * @return Transport parameters of the corresponding SIP Transport
+ */
+const char *sip_transp_param(enum sip_transp tp)
+{
+	switch (tp) {
+
+	case SIP_TRANSP_UDP: return "";
+	case SIP_TRANSP_TCP: return ";transport=tcp";
+	case SIP_TRANSP_TLS: return ";transport=tls";
+	default:             return "";
+	}
+}
+
+
+bool sip_transp_reliable(enum sip_transp tp)
+{
+	switch (tp) {
+
+	case SIP_TRANSP_UDP: return false;
+	case SIP_TRANSP_TCP: return true;
+	case SIP_TRANSP_TLS: return true;
+	default:             return false;
+	}
+}
+
+
+/**
+ * Get the default port number for a given SIP Transport
+ *
+ * @param tp   SIP Transport
+ * @param port Port number
+ *
+ * @return Corresponding port number
+ */
+uint16_t sip_transp_port(enum sip_transp tp, uint16_t port)
+{
+	if (port)
+		return port;
+
+	switch (tp) {
+
+	case SIP_TRANSP_UDP: return SIP_PORT;
+	case SIP_TRANSP_TCP: return SIP_PORT;
+	case SIP_TRANSP_TLS: return SIP_PORT_TLS;
+	default:             return 0;
+	}
+}
+
+
+static bool debug_handler(struct le *le, void *arg)
+{
+	const struct sip_transport *transp = le->data;
+	struct re_printf *pf = arg;
+
+	(void)re_hprintf(pf, "  %J (%s)\n",
+			 &transp->laddr,
+			 sip_transp_name(transp->tp));
+
+	return false;
+}
+
+
+int sip_transp_debug(struct re_printf *pf, const struct sip *sip)
+{
+	int err;
+
+	err = re_hprintf(pf, "transports:\n");
+	list_apply(&sip->transpl, true, debug_handler, pf);
+
+	return err;
+}
+
+
+/**
+ * Get the TCP Connection from a SIP Message
+ *
+ * @param msg SIP Message
+ *
+ * @return TCP Connection if reliable transport, otherwise NULL
+ */
+struct tcp_conn *sip_msg_tcpconn(const struct sip_msg *msg)
+{
+	if (!msg || !msg->sock)
+		return NULL;
+
+	switch (msg->tp) {
+
+	case SIP_TRANSP_TCP:
+	case SIP_TRANSP_TLS:
+		return ((struct sip_conn *)msg->sock)->tc;
+
+	default:
+		return NULL;
+	}
+}
+
+
+int  sip_keepalive_tcp(struct sip_keepalive *ka, struct sip_conn *conn,
+		       uint32_t interval)
+{
+	if (!ka || !conn)
+		return EINVAL;
+
+	if (!conn->tc || !conn->established)
+		return ENOTCONN;
+
+	list_append(&conn->kal, &ka->le, ka);
+
+	if (!tmr_isrunning(&conn->tmr_ka)) {
+
+		interval = MAX(interval ? interval : TCP_KEEPALIVE_INTVAL,
+			       TCP_KEEPALIVE_TIMEOUT * 2);
+
+		conn->ka_interval = interval;
+
+		tmr_start(&conn->tmr_ka, sip_keepalive_wait(conn->ka_interval),
+			  conn_keepalive_handler, conn);
+	}
+
+	return 0;
+}
diff --git a/src/sip/via.c b/src/sip/via.c
new file mode 100644
index 0000000..76e518a
--- /dev/null
+++ b/src/sip/via.c
@@ -0,0 +1,77 @@
+/**
+ * @file via.c  SIP Via decode
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_uri.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_msg.h>
+#include <re_sip.h>
+
+
+static int decode_hostport(const struct pl *hostport, struct pl *host,
+			   struct pl *port)
+{
+	/* Try IPv6 first */
+	if (!re_regex(hostport->p, hostport->l, "\\[[0-9a-f:]+\\][:]*[0-9]*",
+		      host, NULL, port))
+		return 0;
+
+	/* Then non-IPv6 host */
+	return re_regex(hostport->p, hostport->l, "[^:]+[:]*[0-9]*",
+			host, NULL, port);
+}
+
+
+/**
+ * Decode a pointer-length string into a SIP Via header
+ *
+ * @param via SIP Via header
+ * @param pl  Pointer-length string
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int sip_via_decode(struct sip_via *via, const struct pl *pl)
+{
+	struct pl transp, host, port;
+	int err;
+
+	if (!via || !pl)
+		return EINVAL;
+
+	err = re_regex(pl->p, pl->l,
+		       "SIP[  \t\r\n]*/[ \t\r\n]*2.0[ \t\r\n]*/[ \t\r\n]*"
+		       "[A-Z]+[ \t\r\n]*[^; \t\r\n]+[ \t\r\n]*[^]*",
+		       NULL, NULL, NULL, NULL, &transp,
+		       NULL, &via->sentby, NULL, &via->params);
+	if (err)
+		return err;
+
+	if (!pl_strcmp(&transp, "TCP"))
+		via->tp = SIP_TRANSP_TCP;
+	else if (!pl_strcmp(&transp, "TLS"))
+		via->tp = SIP_TRANSP_TLS;
+	else if (!pl_strcmp(&transp, "UDP"))
+		via->tp = SIP_TRANSP_UDP;
+	else
+		via->tp = SIP_TRANSP_NONE;
+
+	err = decode_hostport(&via->sentby, &host, &port);
+	if (err)
+		return err;
+
+	sa_init(&via->addr, AF_INET);
+
+	(void)sa_set(&via->addr, &host, 0);
+
+	if (pl_isset(&port))
+		sa_set_port(&via->addr, pl_u32(&port));
+
+	via->val = *pl;
+
+	return msg_param_decode(&via->params, "branch", &via->branch);
+}