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/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;
+}