Index: /pjproject/trunk/pjnath/include/pjnath/config.h
===================================================================
--- /pjproject/trunk/pjnath/include/pjnath/config.h	(revision 5986)
+++ /pjproject/trunk/pjnath/include/pjnath/config.h	(revision 5987)
@@ -221,4 +221,11 @@
 #endif
 
+/**
+ * Maximum number of TCP data connection to peer(s) that a TURN client can
+ * open/accept for each TURN allocation (or TURN control connection).
+ */
+#ifndef PJ_TURN_MAX_TCP_CONN_CNT
+#   define PJ_TURN_MAX_TCP_CONN_CNT		    8
+#endif
 
 /* **************************************************************************
Index: /pjproject/trunk/pjnath/include/pjnath/stun_msg.h
===================================================================
--- /pjproject/trunk/pjnath/include/pjnath/stun_msg.h	(revision 5986)
+++ /pjproject/trunk/pjnath/include/pjnath/stun_msg.h	(revision 5987)
@@ -94,4 +94,19 @@
 
     /**
+     * STUN/TURN Connect as defined by RFC 6062
+     */
+    PJ_STUN_CONNECT_METHOD		    = 10,
+
+    /**
+     * STUN/TURN ConnectionBind as defined by RFC 6062
+     */
+    PJ_STUN_CONNECTION_BIND_METHOD	    = 11,
+
+    /**
+     * STUN/TURN ConnectionAttempt as defined by RFC 6062
+     */
+    PJ_STUN_CONNECTION_ATTEMPT_METHOD	    = 12,
+
+    /**
      * All known methods.
      */
@@ -292,5 +307,16 @@
      * Error response to STUN ChannelBind request.
      */
-    PJ_STUN_CHANNEL_BIND_ERROR_RESPONSE	    = 0x0119
+    PJ_STUN_CHANNEL_BIND_ERROR_RESPONSE	    = 0x0119,
+
+
+    /**
+     * STUN/TURN ConnectBind Request
+     */
+    PJ_STUN_CONNECTION_BIND_REQUEST	    = 0x000b,
+
+    /**
+     * TURN ConnectionAttempt indication
+     */
+    PJ_STUN_CONNECTION_ATTEMPT_INDICATION   = 0x001c,
 
 } pj_stun_msg_type;
@@ -334,4 +360,5 @@
     PJ_STUN_ATTR_PRIORITY	    = 0x0024,/**< PRIORITY		    */
     PJ_STUN_ATTR_USE_CANDIDATE	    = 0x0025,/**< USE-CANDIDATE		    */
+    PJ_STUN_ATTR_CONNECTION_ID	    = 0x002a,/**< CONNECTION-ID		    */
     PJ_STUN_ATTR_ICMP		    = 0x0030,/**< ICMP (TURN)		    */
 
Index: /pjproject/trunk/pjnath/include/pjnath/turn_session.h
===================================================================
--- /pjproject/trunk/pjnath/include/pjnath/turn_session.h	(revision 5986)
+++ /pjproject/trunk/pjnath/include/pjnath/turn_session.h	(revision 5987)
@@ -299,4 +299,41 @@
 		     pj_turn_state_t new_state);
 
+    /**
+     * Notification when TURN client received a ConnectionAttempt Indication
+     * from the TURN server, which indicates that peer initiates a TCP
+     * connection to allocated slot in the TURN server. Application must
+     * implement this callback if it uses RFC 6062 (TURN TCP allocations).
+     *
+     * After receiving this callback, application should establish a new TCP
+     * connection to the TURN server and send ConnectionBind request (using
+     * pj_turn_session_connection_bind()). After the connection binding
+     * succeeds, this new connection will become a data only connection.
+     *
+     * @param sess	The TURN session.
+     * @param conn_id	The connection ID assigned by TURN server.
+     * @param peer_addr	Peer address that tried to connect to the TURN server.
+     * @param addr_len	Length of the peer address.
+     */
+    void (*on_connection_attempt)(pj_turn_session *sess,
+				  pj_uint32_t conn_id,
+				  const pj_sockaddr_t *peer_addr,
+				  unsigned addr_len);
+
+    /**
+     * Notification for ConnectionBind request sent using
+     * pj_turn_session_connection_bind().
+     *
+     * @param sess	The TURN session.
+     * @param status	The status code.
+     * @param conn_id	The connection ID.
+     * @param peer_addr	Peer address.
+     * @param addr_len	Length of the peer address.
+     */
+    void (*on_connection_bind_status)(pj_turn_session *sess,
+				      pj_status_t status,
+				      pj_uint32_t conn_id,
+				      const pj_sockaddr_t *peer_addr,
+				      unsigned addr_len);
+
 } pj_turn_session_cb;
 
@@ -340,4 +377,11 @@
     int	    af;
 
+    /**
+     * Type of connection to from TURN server to peer. Supported values are
+     * PJ_TURN_TP_UDP (RFC 5766) and PJ_TURN_TP_TCP (RFC 6062)
+     *
+     * Default is PJ_TURN_TP_UDP.
+     */
+    pj_turn_tp_type peer_conn_type;
 
 } pj_turn_alloc_param;
@@ -385,4 +429,38 @@
 
 } pj_turn_session_info;
+
+
+/**
+ * Parameters for function pj_turn_session_on_rx_pkt2().
+ */
+typedef struct pj_turn_session_on_rx_pkt_param
+{
+    /**
+     * The packet as received from the TURN server. This should contain
+     * either STUN encapsulated message or a ChannelData packet.
+     */
+    void		*pkt;
+
+    /**
+     * The length of the packet.
+     */
+    pj_size_t		 pkt_len;
+
+    /**
+     * The number of parsed or processed data from the packet.
+     */
+    pj_size_t		 parsed_len;
+
+    /**
+     * Source address where the packet is received from.
+     */
+    const pj_sockaddr_t	*src_addr;
+
+    /**
+     * Length of the source address.
+     */
+    unsigned		 src_addr_len;
+
+} pj_turn_session_on_rx_pkt_param;
 
 
@@ -742,4 +820,54 @@
 					       pj_size_t *parsed_len);
 
+/**
+ * Notify TURN client session upon receiving a packet from server. Since
+ * the TURN session is transport independent, it does not read packet from
+ * any sockets, and rather relies on application giving it packets that
+ * are received from the TURN server. The session then processes this packet
+ * and decides whether it is part of TURN protocol exchange or if it is a
+ * data to be reported back to user, which in this case it will call the
+ * \a on_rx_data() callback.
+ *
+ * This function is variant of pj_turn_session_on_rx_pkt() with additional
+ * parameters such as source address. Source address will allow STUN/TURN
+ * session to resend the request (e.g: with updated authentication) to the
+ * provided source address which may be different to the initial connection,
+ * for example in RFC 6062 scenario that there can be some data connection
+ * and a control connection.
+ *
+ * @param sess		The TURN client session.
+ * @param prm		The function parameters, e.g: packet, source address.
+ *
+ * @return		The function may return non-PJ_SUCCESS if it receives
+ *			non-STUN and non-ChannelData packet, or if the
+ *			\a on_rx_data() returns non-PJ_SUCCESS;
+ */
+PJ_DECL(pj_status_t) pj_turn_session_on_rx_pkt2(
+				    pj_turn_session *sess,
+				    pj_turn_session_on_rx_pkt_param *prm);
+
+/**
+ * Initiate connection binding to the specified peer using ConnectionBind
+ * request. Application must call this function when it uses RFC 6062
+ * (TURN TCP allocations) to establish a data connection with peer after
+ * opening/accepting connection to/from peer. The connection binding status
+ * will be notified via on_connection_bind_status callback.
+ *
+ * @param sess		The TURN session.
+ * @param pool		The memory pool.
+ * @param conn_id	The connection ID assigned by TURN server.
+ * @param peer_addr	Peer address.
+ * @param addr_len	Length of the peer address.
+ *
+ * @return		PJ_SUCCESS if the operation has been successfully
+ *			issued, or the appropriate error code. Note that
+ *			the operation itself will complete asynchronously.
+ */
+PJ_DECL(pj_status_t) pj_turn_session_connection_bind(
+					    pj_turn_session *sess,
+					    pj_pool_t *pool,
+					    pj_uint32_t conn_id,
+					    const pj_sockaddr_t *peer_addr,
+					    unsigned addr_len);
 
 /**
Index: /pjproject/trunk/pjnath/include/pjnath/turn_sock.h
===================================================================
--- /pjproject/trunk/pjnath/include/pjnath/turn_sock.h	(revision 5986)
+++ /pjproject/trunk/pjnath/include/pjnath/turn_sock.h	(revision 5987)
@@ -98,4 +98,47 @@
 		     pj_turn_state_t old_state,
 		     pj_turn_state_t new_state);
+
+    /**
+     * Notification when TURN client received a ConnectionAttempt Indication
+     * from the TURN server, which indicates that peer initiates a TCP
+     * connection to allocated slot in the TURN server. Application should
+     * implement this callback if it uses RFC 6062 (TURN TCP allocations),
+     * otherwise TURN client will automatically accept it.
+     *
+     * If application accepts the peer connection attempt (i.e: by returning
+     * PJ_SUCCESS or not implementing this callback), the TURN socket will
+     * initiate a new connection to the TURN server and send ConnectionBind
+     * request, and eventually will notify application via
+     * on_connection_status callback, if implemented.
+     *
+     * @param turn_sock	    The TURN client transport.
+     * @param conn_id	    The connection ID assigned by TURN server.
+     * @param peer_addr	    Peer address that tried to connect to the
+     *			    TURN server.
+     * @param addr_len	    Length of the peer address.
+     *
+     * @return		    The callback must return PJ_SUCCESS to accept
+     *			    the connection attempt.
+     */
+    pj_status_t (*on_connection_attempt)(pj_turn_sock *turn_sock,
+					 pj_uint32_t conn_id,
+					 const pj_sockaddr_t *peer_addr,
+					 unsigned addr_len);
+
+    /**
+     * Notification for initiated TCP data connection to peer (RFC 6062),
+     * for example after peer connection attempt is accepted.
+     *
+     * @param turn_sock	    The TURN client transport.
+     * @param status	    The status code.
+     * @param conn_id	    The connection ID.
+     * @param peer_addr	    Peer address.
+     * @param addr_len	    Length of the peer address.
+     */
+    void (*on_connection_status)(pj_turn_sock *turn_sock,
+				 pj_status_t status,
+				 pj_uint32_t conn_id,
+				 const pj_sockaddr_t *peer_addr,
+				 unsigned addr_len);
 
 } pj_turn_sock_cb;
Index: /pjproject/trunk/pjnath/src/pjnath/stun_msg.c
===================================================================
--- /pjproject/trunk/pjnath/src/pjnath/stun_msg.c	(revision 5986)
+++ /pjproject/trunk/pjnath/src/pjnath/stun_msg.c	(revision 5987)
@@ -46,4 +46,7 @@
     "CreatePermission",		/* 8 */
     "ChannelBind",		/* 9 */
+    "Connect",			/* 10 */
+    "ConnectionBind",		/* 11 */
+    "ConnectionAttempt",	/* 12 */
 };
 
@@ -477,9 +480,9 @@
     },
     {
-	/* ID 0x002a is not assigned */
-	NULL,
-	NULL,
-	NULL,
-	NULL
+	/* PJ_STUN_ATTR_CONNECTION_ID, */
+	"CONNECTION-ID",
+	&decode_uint_attr,
+	&encode_uint_attr,
+	&clone_uint_attr
     },
     {
Index: /pjproject/trunk/pjnath/src/pjnath/turn_session.c
===================================================================
--- /pjproject/trunk/pjnath/src/pjnath/turn_session.c	(revision 5986)
+++ /pjproject/trunk/pjnath/src/pjnath/turn_session.c	(revision 5987)
@@ -104,4 +104,10 @@
 };
 
+struct conn_bind_t
+{
+    pj_uint32_t	     id;		/* Connection ID.	*/
+    pj_sockaddr	     peer_addr;		/* Peer address.	*/
+    unsigned	     peer_addr_len;
+};
 
 /* The TURN client session structure */
@@ -209,4 +215,5 @@
 {
     pj_bzero(prm, sizeof(*prm));
+    prm->peer_conn_type = PJ_TURN_TP_UDP;
 }
 
@@ -724,4 +731,7 @@
 		     sess->state<=PJ_TURN_STATE_RESOLVED, 
 		     PJ_EINVALIDOP);
+    PJ_ASSERT_RETURN(param->peer_conn_type == PJ_TURN_TP_UDP ||
+                     param->peer_conn_type == PJ_TURN_TP_TCP,
+                     PJ_EINVAL);
 
     /* Verify address family in allocation param */
@@ -761,5 +771,5 @@
     pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg,
 			      PJ_STUN_ATTR_REQ_TRANSPORT, 
-			      PJ_STUN_SET_RT_PROTO(PJ_TURN_TP_UDP));
+			      PJ_STUN_SET_RT_PROTO(param->peer_conn_type));
 
     /* Include BANDWIDTH if requested */
@@ -999,4 +1009,10 @@
     }
 
+    /* If peer connection is TCP (RFC 6062), send it directly */
+    if (sess->alloc_param.peer_conn_type == PJ_TURN_TP_TCP) {
+	status = sess->cb.on_send_pkt(sess, pkt, pkt_len, addr, addr_len);
+	goto on_return;
+    }
+
     /* See if the peer is bound to a channel number */
     ch = lookup_ch_by_addr(sess, addr, pj_sockaddr_get_len(addr), 
@@ -1139,7 +1155,51 @@
 
 /**
- * Notify TURN client session upon receiving a packet from server.
- * The packet maybe a STUN packet or ChannelData packet.
- */
+ * Send ConnectionBind request.
+ */
+PJ_DEF(pj_status_t) pj_turn_session_connection_bind(
+					    pj_turn_session *sess,
+					    pj_pool_t *pool,
+					    pj_uint32_t conn_id,
+					    const pj_sockaddr_t *peer_addr,
+					    unsigned addr_len)
+{
+    pj_stun_tx_data *tdata;
+    struct conn_bind_t *conn_bind;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(sess && pool && conn_id && peer_addr && addr_len,
+		     PJ_EINVAL);
+    PJ_ASSERT_RETURN(sess->state == PJ_TURN_STATE_READY, PJ_EINVALIDOP);
+
+    pj_grp_lock_acquire(sess->grp_lock);
+
+    /* Create blank ConnectionBind request */
+    status = pj_stun_session_create_req(sess->stun, 
+					PJ_STUN_CONNECTION_BIND_REQUEST,
+					PJ_STUN_MAGIC, NULL, &tdata);
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
+    /* Add CONNECTION_ID attribute */
+    pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg,
+			      PJ_STUN_ATTR_CONNECTION_ID,
+			      conn_id);
+
+    conn_bind = PJ_POOL_ZALLOC_T(pool, struct conn_bind_t);
+    conn_bind->id = conn_id;
+    pj_sockaddr_cp(&conn_bind->peer_addr, peer_addr);
+    conn_bind->peer_addr_len = addr_len;
+
+    /* Send the request, associate connection data structure with tdata 
+     * for future reference when we receive the ConnectionBind response.
+     */
+    status = pj_stun_session_send_msg(sess->stun, conn_bind, PJ_FALSE,
+				      PJ_FALSE, peer_addr, addr_len, tdata);
+
+on_return:
+    pj_grp_lock_release(sess->grp_lock);
+    return status;
+}
+
 PJ_DEF(pj_status_t) pj_turn_session_on_rx_pkt(pj_turn_session *sess,
 					      void *pkt,
@@ -1147,4 +1207,24 @@
 					      pj_size_t *parsed_len)
 {
+    pj_turn_session_on_rx_pkt_param prm;
+    pj_status_t status;
+    
+    pj_bzero(&prm, sizeof(prm));
+    prm.pkt = pkt;
+    prm.pkt_len = pkt_len;
+    status = pj_turn_session_on_rx_pkt2(sess, &prm);
+    if (status == PJ_SUCCESS && parsed_len)
+	*parsed_len = prm.parsed_len;
+    return status;
+}
+
+/**
+ * Notify TURN client session upon receiving a packet from server.
+ * The packet maybe a STUN packet or ChannelData packet.
+ */
+PJ_DEF(pj_status_t) pj_turn_session_on_rx_pkt2(
+				pj_turn_session *sess,
+				pj_turn_session_on_rx_pkt_param *prm)
+{
     pj_bool_t is_stun;
     pj_status_t status;
@@ -1161,17 +1241,20 @@
 
     /* Quickly check if this is STUN message */
-    is_stun = ((((pj_uint8_t*)pkt)[0] & 0xC0) == 0);
+    is_stun = ((((pj_uint8_t*)prm->pkt)[0] & 0xC0) == 0);
 
     if (is_stun) {
 	/* This looks like STUN, give it to the STUN session */
 	unsigned options;
+	const pj_sockaddr_t *src_addr = prm->src_addr?
+					prm->src_addr:sess->srv_addr;
+	unsigned src_addr_len = prm->src_addr_len? prm->src_addr_len:
+				pj_sockaddr_get_len(sess->srv_addr);
 
 	options = PJ_STUN_CHECK_PACKET | PJ_STUN_NO_FINGERPRINT_CHECK;
 	if (is_datagram)
 	    options |= PJ_STUN_IS_DATAGRAM;
-	status=pj_stun_session_on_rx_pkt(sess->stun, pkt, pkt_len,
-					 options, NULL, parsed_len,
-					 sess->srv_addr,
-					 pj_sockaddr_get_len(sess->srv_addr));
+	status=pj_stun_session_on_rx_pkt(sess->stun, prm->pkt, prm->pkt_len,
+					 options, NULL, &prm->parsed_len,
+					 src_addr, src_addr_len);
 
     } else {
@@ -1180,32 +1263,28 @@
 	struct ch_t *ch;
 
-	if (pkt_len < 4) {
-	    if (parsed_len) *parsed_len = 0;
+	if (prm->pkt_len < 4) {
+	    prm->parsed_len = 0;
 	    return PJ_ETOOSMALL;
 	}
 
 	/* Decode ChannelData packet */
-	pj_memcpy(&cd, pkt, sizeof(pj_turn_channel_data));
+	pj_memcpy(&cd, prm->pkt, sizeof(pj_turn_channel_data));
 	cd.ch_number = pj_ntohs(cd.ch_number);
 	cd.length = pj_ntohs(cd.length);
 
 	/* Check that size is sane */
-	if (pkt_len < cd.length+sizeof(cd)) {
-	    if (parsed_len) {
-		if (is_datagram) {
-		    /* Discard the datagram */
-		    *parsed_len = pkt_len;
-		} else {
-		    /* Insufficient fragment */
-		    *parsed_len = 0;
-		}
+	if (prm->pkt_len < cd.length+sizeof(cd)) {
+	    if (is_datagram) {
+		/* Discard the datagram */
+		prm->parsed_len = prm->pkt_len;
+	    } else {
+		/* Insufficient fragment */
+		prm->parsed_len = 0;
 	    }
 	    status = PJ_ETOOSMALL;
 	    goto on_return;
 	} else {
-	    if (parsed_len) {
-		/* Apply padding too */
-		*parsed_len = ((cd.length + 3) & (~3)) + sizeof(cd);
-	    }
+	    /* Apply padding too */
+	    prm->parsed_len = ((cd.length + 3) & (~3)) + sizeof(cd);
 	}
 
@@ -1219,5 +1298,5 @@
 	/* Notify application */
 	if (sess->cb.on_rx_data) {
-	    (*sess->cb.on_rx_data)(sess, ((pj_uint8_t*)pkt)+sizeof(cd), 
+	    (*sess->cb.on_rx_data)(sess, ((pj_uint8_t*)prm->pkt)+sizeof(cd), 
 				   cd.length, &ch->addr,
 				   pj_sockaddr_get_len(&ch->addr));
@@ -1643,4 +1722,34 @@
 	}
 
+    } else if (method == PJ_STUN_CONNECTION_BIND_METHOD) {
+	/* Handle ConnectionBind response */
+	struct conn_bind_t *conn_bind = (struct conn_bind_t*)token;
+
+	if (status != PJ_SUCCESS ||
+	    !PJ_STUN_IS_SUCCESS_RESPONSE(response->hdr.type)) 
+	{
+	    pj_str_t reason = {0};
+	    if (status == PJ_SUCCESS) {
+		const pj_stun_errcode_attr *err_attr;
+		err_attr = (const pj_stun_errcode_attr*)
+			   pj_stun_msg_find_attr(response,
+						 PJ_STUN_ATTR_ERROR_CODE, 0);
+		if (err_attr) {
+		    status = PJ_STATUS_FROM_STUN_CODE(err_attr->err_code);
+		    reason = err_attr->reason;
+		} else {
+		    status = PJNATH_EINSTUNMSG;
+		}
+	    }
+	    pj_perror(1, sess->obj_name, status, "ConnectionBind failed: %.*s",
+		      (int)reason.slen, reason.ptr);
+	}
+
+	/* Notify app */
+	if (sess->cb.on_connection_bind_status) {
+	    (*sess->cb.on_connection_bind_status)
+			(sess, status, conn_bind->id,
+			&conn_bind->peer_addr, conn_bind->peer_addr_len);
+	}
     } else {
 	PJ_LOG(4,(sess->obj_name, "Unexpected STUN %s response",
@@ -1675,5 +1784,36 @@
     sess = (pj_turn_session*)pj_stun_session_get_user_data(stun);
 
-    /* Expecting Data Indication only */
+    /* ConnectionAttempt Indication */
+    if (msg->hdr.type == PJ_STUN_CONNECTION_ATTEMPT_INDICATION) {
+        pj_stun_uint_attr *connection_id_attr;
+
+        /* Get CONNECTION-ID attribute */
+        connection_id_attr = (pj_stun_uint_attr*)
+            pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_CONNECTION_ID, 0);
+
+        /* Get XOR-PEER-ADDRESS attribute */
+        peer_attr = (pj_stun_xor_peer_addr_attr*)
+            pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_XOR_PEER_ADDR, 0);
+
+        /* Must have both XOR-PEER-ADDRESS and CONNECTION-ID attributes */
+        if (!peer_attr || !connection_id_attr) {
+            PJ_LOG(4,(sess->obj_name, 
+                      "Received ConnectionAttempt indication with missing "
+		      "attributes"));
+            return PJ_EINVALIDOP;
+        }
+
+        /* Notify application */
+        if (sess->cb.on_connection_attempt) {
+            (*sess->cb.on_connection_attempt)
+				(sess,
+				connection_id_attr->value,
+				&peer_attr->sockaddr,
+                                pj_sockaddr_get_len(&peer_attr->sockaddr));
+        }
+        return PJ_SUCCESS;
+    }
+
+    /* Next, expecting Data Indication only */
     if (msg->hdr.type != PJ_STUN_DATA_INDICATION) {
 	PJ_LOG(4,(sess->obj_name, "Unexpected STUN %s indication",
Index: /pjproject/trunk/pjnath/src/pjnath/turn_sock.c
===================================================================
--- /pjproject/trunk/pjnath/src/pjnath/turn_sock.c	(revision 5986)
+++ /pjproject/trunk/pjnath/src/pjnath/turn_sock.c	(revision 5987)
@@ -39,4 +39,30 @@
 #define INIT	0x1FFFFFFF
 
+enum {
+    DATACONN_STATE_NULL,
+    DATACONN_STATE_INITSOCK,
+    DATACONN_STATE_CONN_BINDING,
+    DATACONN_STATE_READY,
+};
+
+/* This structure describe data connection of TURN TCP allocations
+ * (RFC 6062).
+ */
+typedef struct tcp_data_conn_t
+{
+    pj_pool_t		 *pool;
+
+    pj_uint32_t		 id;		/* Connection ID.		*/
+    int			 state;		/* Connection state.		*/
+    pj_sockaddr		 peer_addr;	/* Peer address (mapped).	*/
+    unsigned		 peer_addr_len;
+
+    pj_activesock_t	*asock;		/* Active socket.		*/
+    pj_ioqueue_op_key_t  send_key;
+
+    pj_turn_sock	*turn_sock;	/* TURN socket parent.		*/
+} tcp_data_conn_t;
+
+
 struct pj_turn_sock
 {
@@ -60,4 +86,8 @@
     pj_activesock_t	*active_sock;
     pj_ioqueue_op_key_t	 send_key;
+
+    /* Data connection, when peer_conn_type==PJ_TURN_TP_TCP (RFC 6062) */
+    unsigned		 data_conn_cnt;
+    tcp_data_conn_t	 data_conn[PJ_TURN_MAX_TCP_CONN_CNT];
 };
 
@@ -83,4 +113,13 @@
 			  pj_turn_state_t old_state,
 			  pj_turn_state_t new_state);
+static void turn_on_connection_attempt(pj_turn_session *sess,
+				       pj_uint32_t conn_id,
+				       const pj_sockaddr_t *peer_addr,
+				       unsigned addr_len);
+static void turn_on_connection_bind_status(pj_turn_session *sess,
+					   pj_status_t status,
+					   pj_uint32_t conn_id,
+					   const pj_sockaddr_t *peer_addr,
+					   unsigned addr_len);
 
 static pj_bool_t on_data_read(pj_activesock_t *asock,
@@ -92,5 +131,12 @@
 				     pj_status_t status);
 
-
+static pj_bool_t dataconn_on_data_read(pj_activesock_t *asock,
+				       void *data,
+				       pj_size_t size,
+				       pj_status_t status,
+				       pj_size_t *remainder);
+static pj_bool_t dataconn_on_connect_complete(pj_activesock_t *asock,
+					      pj_status_t status);
+static void dataconn_cleanup(tcp_data_conn_t *conn);
 
 static void turn_sock_on_destroy(void *comp);
@@ -194,4 +240,6 @@
     sess_cb.on_rx_data = &turn_on_rx_data;
     sess_cb.on_state = &turn_on_state;
+    sess_cb.on_connection_attempt = &turn_on_connection_attempt;
+    sess_cb.on_connection_bind_status = &turn_on_connection_bind_status;
     status = pj_turn_session_create(cfg, pool->obj_name, af, conn_type,
                                     turn_sock->grp_lock, &sess_cb, 0,
@@ -225,4 +273,6 @@
 static void destroy(pj_turn_sock *turn_sock)
 {
+    unsigned i;
+
     PJ_LOG(4,(turn_sock->obj_name, "TURN socket destroy request, ref_cnt=%d",
 	      pj_grp_lock_get_ref(turn_sock->grp_lock)));
@@ -239,4 +289,10 @@
     if (turn_sock->active_sock)
 	pj_activesock_close(turn_sock->active_sock);
+
+    for (i=0; i < PJ_TURN_MAX_TCP_CONN_CNT; ++i) {
+	dataconn_cleanup(&turn_sock->data_conn[i]);
+    }
+    turn_sock->data_conn_cnt = 0;
+
     pj_grp_lock_dec_ref(turn_sock->grp_lock);
     pj_grp_lock_release(turn_sock->grp_lock);
@@ -668,5 +724,5 @@
 			      pj_turn_session_get_user_data(sess);
     pj_ssize_t len = pkt_len;
-    pj_status_t status;
+    pj_status_t status = PJ_SUCCESS;
 
     if (turn_sock == NULL || turn_sock->is_destroying) {
@@ -681,8 +737,33 @@
 				      &turn_sock->send_key, pkt, &len, 0,
 				      dst_addr, dst_addr_len);
+    } else if (turn_sock->alloc_param.peer_conn_type == PJ_TURN_TP_TCP) {
+	pj_turn_session_info info;
+	pj_turn_session_get_info(turn_sock->sess, &info);
+	if (pj_sockaddr_cmp(&info.server, dst_addr) == 0) {
+	    /* Destination address is TURN server */
+	    status = pj_activesock_send(turn_sock->active_sock,
+					&turn_sock->send_key, pkt, &len, 0);
+	} else {
+	    /* Destination address is peer, lookup data connection */
+	    unsigned i;
+
+	    status = PJ_ENOTFOUND;
+	    for (i=0; i < PJ_TURN_MAX_TCP_CONN_CNT; ++i) {
+		tcp_data_conn_t *conn = &turn_sock->data_conn[i];
+		if (conn->state < DATACONN_STATE_CONN_BINDING)
+		    continue;
+		if (pj_sockaddr_cmp(&conn->peer_addr, dst_addr) == 0) {
+		    status = pj_activesock_send(conn->asock,
+						&conn->send_key,
+						pkt, &len, 0);
+		    break;
+		}
+	    }
+	}
     } else {
 	status = pj_activesock_send(turn_sock->active_sock,
 				    &turn_sock->send_key, pkt, &len, 0);
     }
+
     if (status != PJ_SUCCESS && status != PJ_EPENDING) {
 	show_err(turn_sock, "socket send()", status);
@@ -721,4 +802,9 @@
     if (turn_sock == NULL || turn_sock->is_destroying) {
 	/* We've been destroyed */
+	return;
+    }
+
+    if (turn_sock->alloc_param.peer_conn_type != PJ_TURN_TP_UDP) {
+	/* Data traffic for RFC 6062 is not via TURN session */
 	return;
     }
@@ -937,2 +1023,337 @@
 
 
+static void dataconn_cleanup(tcp_data_conn_t *conn)
+{
+    if (conn->asock)
+	pj_activesock_close(conn->asock);
+
+    pj_pool_safe_release(&conn->pool);
+
+    pj_bzero(conn, sizeof(conn));
+}
+
+static pj_bool_t dataconn_on_data_read(pj_activesock_t *asock,
+				       void *data,
+				       pj_size_t size,
+				       pj_status_t status,
+				       pj_size_t *remainder)
+{
+    tcp_data_conn_t *conn = (tcp_data_conn_t*)
+			    pj_activesock_get_user_data(asock);
+    pj_turn_sock *turn_sock = conn->turn_sock;
+
+    pj_grp_lock_acquire(turn_sock->grp_lock);
+
+    if (size == 0 && status != PJ_SUCCESS) {
+	/* Connection gone, release data connection */
+	dataconn_cleanup(conn);
+	--turn_sock->data_conn_cnt;
+	pj_grp_lock_release(turn_sock->grp_lock);
+	return PJ_FALSE;
+    }
+
+    if (conn->state == DATACONN_STATE_READY) {
+	/* Application data */
+	if (turn_sock->cb.on_rx_data) {
+	    (*turn_sock->cb.on_rx_data)(turn_sock, data, size,
+					&conn->peer_addr,
+					conn->peer_addr_len);
+	}
+    } else if (conn->state == DATACONN_STATE_CONN_BINDING) {
+	/* Waiting for ConnectionBind response */
+	pj_bool_t is_stun;
+	pj_turn_session_on_rx_pkt_param prm;
+        
+	/* Ignore if this is not a STUN message */
+	is_stun = ((((pj_uint8_t*)data)[0] & 0xC0) == 0);
+	if (!is_stun)
+	    goto on_return;
+
+	pj_bzero(&prm, sizeof(prm));
+	prm.pkt = data;
+	prm.pkt_len = size;
+	prm.src_addr = &conn->peer_addr;
+	prm.src_addr_len = conn->peer_addr_len;
+	pj_turn_session_on_rx_pkt2(conn->turn_sock->sess, &prm);
+	/* Got remainder? */
+	if (prm.parsed_len < size) {
+	    *remainder = size - prm.parsed_len;
+	    if (prm.parsed_len) {
+		pj_memmove(data, (pj_uint8_t*)data+prm.parsed_len,
+			   *remainder);
+	    }
+	}
+    }
+
+on_return:
+    pj_grp_lock_release(turn_sock->grp_lock);
+    return PJ_TRUE;
+}
+
+static pj_bool_t dataconn_on_connect_complete(pj_activesock_t *asock,
+					      pj_status_t status)
+{
+    tcp_data_conn_t *conn = (tcp_data_conn_t*)
+			    pj_activesock_get_user_data(asock);
+    pj_turn_sock *turn_sock = conn->turn_sock;
+
+    pj_grp_lock_acquire(turn_sock->grp_lock);
+
+    if (status == PJ_SUCCESS) {
+	status = pj_activesock_start_read(asock, turn_sock->pool,
+					  turn_sock->setting.max_pkt_size, 0);
+    }
+    if (status == PJ_SUCCESS) {
+	conn->state = DATACONN_STATE_CONN_BINDING;
+	status = pj_turn_session_connection_bind(turn_sock->sess,
+						 conn->pool,
+						 conn->id,
+						 &conn->peer_addr,
+						 conn->peer_addr_len);
+    }
+    if (status != PJ_SUCCESS) {
+	dataconn_cleanup(conn);
+	--turn_sock->data_conn_cnt;
+	pj_grp_lock_release(turn_sock->grp_lock);
+	return PJ_FALSE;
+    }
+
+    pj_grp_lock_release(turn_sock->grp_lock);
+    return PJ_TRUE;
+}
+
+
+static void turn_on_connection_attempt(pj_turn_session *sess,
+				       pj_uint32_t conn_id,
+				       const pj_sockaddr_t *peer_addr,
+				       unsigned addr_len)
+{
+    pj_turn_sock *turn_sock = (pj_turn_sock*) 
+			      pj_turn_session_get_user_data(sess);
+    pj_pool_t *pool;
+    tcp_data_conn_t *new_conn;
+    pj_turn_session_info info;
+    pj_sock_t sock = PJ_INVALID_SOCKET;
+    pj_activesock_cfg asock_cfg;
+    pj_activesock_cb asock_cb;
+    pj_sockaddr bound_addr, *cfg_bind_addr;
+    pj_uint16_t max_bind_retry;
+    char addrtxt[PJ_INET6_ADDRSTRLEN+8];
+    pj_status_t status;
+    unsigned i;
+
+    PJ_ASSERT_ON_FAIL(turn_sock->conn_type == PJ_TURN_TP_TCP &&
+		      turn_sock->alloc_param.peer_conn_type == PJ_TURN_TP_TCP,
+		      return);
+
+    PJ_LOG(5,(turn_sock->pool->obj_name, "Connection attempt from peer %s",
+	      pj_sockaddr_print(&peer_addr, addrtxt, sizeof(addrtxt), 3)));
+
+    if (turn_sock == NULL) {
+	/* We've been destroyed */
+	return;
+    }
+
+    pj_grp_lock_acquire(turn_sock->grp_lock);
+
+    if (turn_sock->data_conn_cnt == PJ_TURN_MAX_TCP_CONN_CNT) {
+	/* Data connection has reached limit */
+	pj_grp_lock_release(turn_sock->grp_lock);
+	return;
+    }
+
+    /* Check if app wants to accept this connection */
+    status = PJ_SUCCESS;
+    if (turn_sock->cb.on_connection_attempt) {
+	status = (*turn_sock->cb.on_connection_attempt)(turn_sock, conn_id,
+							peer_addr, addr_len);
+    }
+    /* App rejects it */
+    if (status != PJ_SUCCESS) {
+	pj_perror(4, turn_sock->pool->obj_name, status,
+		  "Rejected connection attempt from peer %s",
+		  pj_sockaddr_print(peer_addr, addrtxt, sizeof(addrtxt), 3));
+	pj_grp_lock_release(turn_sock->grp_lock);
+	return;
+    }
+
+    /* Find free data connection slot */
+    for (i=0; i < PJ_TURN_MAX_TCP_CONN_CNT; ++i) {
+	if (turn_sock->data_conn[i].state == DATACONN_STATE_NULL)
+	    break;
+    }
+    pj_assert(i < turn_sock->data_conn_cnt);
+    ++turn_sock->data_conn_cnt;
+
+    /* Init new data connection */
+    new_conn = &turn_sock->data_conn[i];
+    pj_bzero(new_conn, sizeof(*new_conn));
+    pool = pj_pool_create(turn_sock->cfg.pf, "dataconn", 128, 128, NULL);
+    new_conn->pool = pool;
+    new_conn->id = conn_id;
+    new_conn->turn_sock = turn_sock;
+    pj_sockaddr_cp(&new_conn->peer_addr, peer_addr);
+    new_conn->peer_addr_len = addr_len;
+    pj_ioqueue_op_key_init(&new_conn->send_key, sizeof(new_conn->send_key));
+    new_conn->state = DATACONN_STATE_INITSOCK;
+
+    /* Init socket */
+    status = pj_sock_socket(turn_sock->af, pj_SOCK_STREAM(), 0, &sock);
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
+    /* Bind socket */
+    cfg_bind_addr = &turn_sock->setting.bound_addr;
+    max_bind_retry = MAX_BIND_RETRY;
+    if (turn_sock->setting.port_range &&
+	turn_sock->setting.port_range < max_bind_retry)
+    {
+	max_bind_retry = turn_sock->setting.port_range;
+    }
+    pj_sockaddr_init(turn_sock->af, &bound_addr, NULL, 0);
+    if (cfg_bind_addr->addr.sa_family == pj_AF_INET() ||
+	cfg_bind_addr->addr.sa_family == pj_AF_INET6())
+    {
+	pj_sockaddr_cp(&bound_addr, cfg_bind_addr);
+    }
+    status = pj_sock_bind_random(sock, &bound_addr,
+				 turn_sock->setting.port_range,
+				 max_bind_retry);
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
+    /* Apply socket buffer size */
+    if (turn_sock->setting.so_rcvbuf_size > 0) {
+	unsigned sobuf_size = turn_sock->setting.so_rcvbuf_size;
+	status = pj_sock_setsockopt_sobuf(sock, pj_SO_RCVBUF(), PJ_TRUE,
+					  &sobuf_size);
+	if (status != PJ_SUCCESS) {
+	    pj_perror(3, turn_sock->obj_name, status,
+		      "Failed setting SO_RCVBUF");
+	} else {
+	    if (sobuf_size < turn_sock->setting.so_rcvbuf_size) {
+		PJ_LOG(4, (turn_sock->obj_name,
+			   "Warning! Cannot set SO_RCVBUF as configured,"
+			   " now=%d, configured=%d", sobuf_size,
+			   turn_sock->setting.so_rcvbuf_size));
+	    } else {
+		PJ_LOG(5, (turn_sock->obj_name, "SO_RCVBUF set to %d",
+			   sobuf_size));
+	    }
+	}
+    }
+    if (turn_sock->setting.so_sndbuf_size > 0) {
+	unsigned sobuf_size = turn_sock->setting.so_sndbuf_size;
+	status = pj_sock_setsockopt_sobuf(sock, pj_SO_SNDBUF(), PJ_TRUE,
+					  &sobuf_size);
+	if (status != PJ_SUCCESS) {
+	    pj_perror(3, turn_sock->obj_name, status,
+		      "Failed setting SO_SNDBUF");
+	} else {
+	    if (sobuf_size < turn_sock->setting.so_sndbuf_size) {
+		PJ_LOG(4, (turn_sock->obj_name,
+			   "Warning! Cannot set SO_SNDBUF as configured,"
+			   " now=%d, configured=%d", sobuf_size,
+			   turn_sock->setting.so_sndbuf_size));
+	    } else {
+		PJ_LOG(5, (turn_sock->obj_name, "SO_SNDBUF set to %d",
+			   sobuf_size));
+	    }
+	}
+    }
+
+    /* Create active socket */
+    pj_activesock_cfg_default(&asock_cfg);
+    asock_cfg.grp_lock = turn_sock->grp_lock;
+
+    pj_bzero(&asock_cb, sizeof(asock_cb));
+    asock_cb.on_data_read = &dataconn_on_data_read;
+    asock_cb.on_connect_complete = &dataconn_on_connect_complete;
+    status = pj_activesock_create(pool, sock,
+				  pj_SOCK_STREAM(), &asock_cfg,
+				  turn_sock->cfg.ioqueue, &asock_cb,
+				  new_conn, &new_conn->asock);
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
+    /* Connect to TURN server for data connection */
+    pj_turn_session_get_info(turn_sock->sess, &info);
+    status = pj_activesock_start_connect(new_conn->asock,
+					 pool,
+					 &info.server,
+					 pj_sockaddr_get_len(&info.server));
+    if (status == PJ_SUCCESS) {
+	dataconn_on_connect_complete(new_conn->asock, PJ_SUCCESS);
+	pj_grp_lock_release(turn_sock->grp_lock);
+	return;
+    }
+
+on_return:
+    if (status == PJ_EPENDING) {
+	PJ_LOG(5,(pool->obj_name,
+		  "Accepting connection from peer %s",
+		  pj_sockaddr_print(peer_addr, addrtxt, sizeof(addrtxt), 3)));
+    } else {
+	/* not PJ_SUCCESS */
+	pj_perror(4, pool->obj_name, status,
+		  "Failed in accepting connection from peer %s",
+		  pj_sockaddr_print(peer_addr, addrtxt, sizeof(addrtxt), 3));
+
+	if (!new_conn->asock && sock != PJ_INVALID_SOCKET)
+	    pj_sock_close(sock);    
+
+	dataconn_cleanup(new_conn);
+	--turn_sock->data_conn_cnt;
+
+	/* Notify app for failure */
+	if (turn_sock->cb.on_connection_status) {
+	    (*turn_sock->cb.on_connection_status)(turn_sock, status, conn_id,
+						  peer_addr, addr_len);
+	}
+    }
+    pj_grp_lock_release(turn_sock->grp_lock);
+}
+
+static void turn_on_connection_bind_status(pj_turn_session *sess,
+					   pj_status_t status,
+					   pj_uint32_t conn_id,
+					   const pj_sockaddr_t *peer_addr,
+					   unsigned addr_len)
+{
+    pj_turn_sock *turn_sock = (pj_turn_sock*) 
+			      pj_turn_session_get_user_data(sess);
+    tcp_data_conn_t *conn = NULL;
+    unsigned i;
+
+    pj_grp_lock_acquire(turn_sock->grp_lock);
+
+    for (i=0; i < PJ_TURN_MAX_TCP_CONN_CNT; ++i) {
+	tcp_data_conn_t *c = &turn_sock->data_conn[i];
+	if (c->id == conn_id &&
+	    pj_sockaddr_cmp(peer_addr, &c->peer_addr) == 0)
+	{
+	    conn = c;
+	    break;
+	}
+    }
+    if (!conn) {
+	PJ_LOG(5,(turn_sock->pool->obj_name,
+		  "Warning: stray connection bind event"));
+	pj_grp_lock_release(turn_sock->grp_lock);
+	return;
+    }
+
+    if (status == PJ_SUCCESS) {
+	conn->state = DATACONN_STATE_READY;
+    } else {
+	dataconn_cleanup(conn);
+	--turn_sock->data_conn_cnt;
+    }
+
+    pj_grp_lock_release(turn_sock->grp_lock);
+
+    if (turn_sock->cb.on_connection_status) {
+	(*turn_sock->cb.on_connection_status)(turn_sock, status, conn_id,
+					      peer_addr, addr_len);
+    }
+}
