mirror of
https://github.com/archlinuxarm/PKGBUILDs.git
synced 2024-11-08 22:45:43 +00:00
919 lines
29 KiB
Diff
919 lines
29 KiB
Diff
Copyright (c) 2017 Savoir-faire Linux Inc.
|
|
|
|
turn: rfc 6062 support
|
|
|
|
Current implementation of TURN and STUN doesn't support TCP as a valid
|
|
peer connection. This connection is defined by the rfc 6062.
|
|
This patch is an implementation proposal of this rfc into PJNATH.
|
|
Modifications are API backware compatible, not ABI.
|
|
Users must rebuild their code.
|
|
|
|
Written by
|
|
Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
|
|
on behalf of Savoir-faire Linux.
|
|
|
|
----
|
|
|
|
--- a/pjnath/include/pjnath/config.h
|
|
+++ b/pjnath/include/pjnath/config.h
|
|
@@ -220,6 +220,14 @@
|
|
# define PJ_TURN_KEEP_ALIVE_SEC 15
|
|
#endif
|
|
|
|
+/**
|
|
+ * Maximal number of TCP data connection that a client can open/accept with
|
|
+ * peers.
|
|
+ */
|
|
+#ifndef PJ_TURN_MAX_TCP_CNX
|
|
+# define PJ_TURN_MAX_TCP_CNX 32
|
|
+#endif
|
|
+
|
|
|
|
/* **************************************************************************
|
|
* ICE CONFIGURATION
|
|
--- a/pjnath/include/pjnath/stun_msg.h
|
|
+++ b/pjnath/include/pjnath/stun_msg.h
|
|
@@ -92,6 +92,21 @@
|
|
*/
|
|
PJ_STUN_CHANNEL_BIND_METHOD = 9,
|
|
|
|
+ /*
|
|
+ * 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.
|
|
*/
|
|
@@ -261,6 +276,16 @@
|
|
*/
|
|
PJ_STUN_DATA_INDICATION = 0x0017,
|
|
|
|
+ /**
|
|
+ * STUN/TURN ConnectBind Request
|
|
+ */
|
|
+ PJ_STUN_CONNECTION_BIND_REQUEST = 0x000b,
|
|
+
|
|
+ /**
|
|
+ * TURN ConnectionAttempt indication
|
|
+ */
|
|
+ PJ_STUN_CONNECTION_ATTEMPT_INDICATION = 0x001c,
|
|
+
|
|
|
|
/**
|
|
* TURN CreatePermission request
|
|
@@ -333,6 +358,7 @@
|
|
PJ_STUN_ATTR_XOR_REFLECTED_FROM = 0x0023,/**< XOR-REFLECTED-FROM */
|
|
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) */
|
|
|
|
PJ_STUN_ATTR_END_MANDATORY_ATTR,
|
|
--- a/pjnath/include/pjnath/stun_session.h
|
|
+++ b/pjnath/include/pjnath/stun_session.h
|
|
@@ -751,6 +751,13 @@
|
|
pj_stun_tx_data *tdata);
|
|
|
|
|
|
+PJ_DEF(void) pj_stun_session_get_server_cred(
|
|
+ pj_stun_session *sess,
|
|
+ pj_pool_t *pool, pj_str_t *nonce, pj_str_t *realm);
|
|
+
|
|
+PJ_DEF(void) pj_stun_session_set_server_cred(
|
|
+ pj_stun_session *sess, const pj_str_t *nonce, pj_str_t *realm);
|
|
+
|
|
/**
|
|
* @}
|
|
*/
|
|
--- a/pjnath/include/pjnath/turn_session.h
|
|
+++ b/pjnath/include/pjnath/turn_session.h
|
|
@@ -184,6 +184,12 @@
|
|
PJ_TURN_STATE_ALLOCATING,
|
|
|
|
/**
|
|
+ * TURN session has issued CONNECTION-BIND request and is waiting for response
|
|
+ * from the TURN server.
|
|
+ */
|
|
+ PJ_TURN_STATE_CONNECTION_BINDING,
|
|
+
|
|
+ /**
|
|
* TURN session has successfully allocated relay resoruce and now is
|
|
* ready to be used.
|
|
*/
|
|
@@ -298,6 +304,21 @@
|
|
pj_turn_state_t old_state,
|
|
pj_turn_state_t new_state);
|
|
|
|
+ /**
|
|
+ * Notification when TURN session get a ConnectionAttempt indication.
|
|
+ *
|
|
+ * @param sess The TURN session.
|
|
+ * @param conn_id The connection-id to use for connection binding.
|
|
+ * @param peer_addr Peer address that tried to connect on the TURN server.
|
|
+ * @param addr_len Length of the peer address.
|
|
+
|
|
+ */
|
|
+ void (*on_peer_connection)(pj_turn_session *sess,
|
|
+ pj_uint32_t conn_id,
|
|
+ const pj_sockaddr_t *peer_addr,
|
|
+ unsigned addr_len,
|
|
+ pj_status_t status);
|
|
+
|
|
} pj_turn_session_cb;
|
|
|
|
|
|
@@ -339,6 +360,14 @@
|
|
*/
|
|
int af;
|
|
|
|
+ /**
|
|
+ * Type of connection to from TURN server to peer.
|
|
+ *
|
|
+ * Supported values: PJ_TURN_TP_UDP (rfc 5766), PJ_TURN_TP_TLS (rfc 6062)
|
|
+ *
|
|
+ * Default is PJ_TURN_TP_UDP.
|
|
+ */
|
|
+ pj_turn_tp_type peer_conn_type;
|
|
|
|
} pj_turn_alloc_param;
|
|
|
|
@@ -741,6 +770,15 @@
|
|
pj_size_t pkt_len,
|
|
pj_size_t *parsed_len);
|
|
|
|
+/**
|
|
+ * rfc6062
|
|
+ */
|
|
+PJ_DEF(void) pj_turn_session_get_server_cred(
|
|
+ pj_turn_session *sess,
|
|
+ pj_pool_t *pool, pj_str_t *nonce, pj_str_t *realm);
|
|
+
|
|
+PJ_DEF(void) pj_turn_session_set_server_cred(
|
|
+ pj_turn_session *sess, const pj_str_t *nonce, pj_str_t *realm);
|
|
|
|
/**
|
|
* @}
|
|
--- a/pjnath/include/pjnath/turn_sock.h
|
|
+++ b/pjnath/include/pjnath/turn_sock.h
|
|
@@ -98,6 +98,23 @@
|
|
pj_turn_state_t old_state,
|
|
pj_turn_state_t new_state);
|
|
|
|
+ /**
|
|
+ * Notification when TURN session get a ConnectionAttempt indication.
|
|
+ *
|
|
+ * @param turn_sock The TURN client transport.
|
|
+ * @param conn_id The connection-id to use for connection binding.
|
|
+ * @param peer_addr Peer address that tried to connect on the TURN server.
|
|
+ * @param addr_len Length of the peer address.
|
|
+ * @param status PJ_SUCCESS when connection is made, or any errors
|
|
+ * if the connection has failed (or if the peer has
|
|
+ * disconnected after an established connection).
|
|
+ */
|
|
+ void (*on_peer_connection)(pj_turn_sock *turn_sock,
|
|
+ pj_uint32_t conn_id,
|
|
+ const pj_sockaddr_t *peer_addr,
|
|
+ unsigned addr_len,
|
|
+ pj_status_t status);
|
|
+
|
|
} pj_turn_sock_cb;
|
|
|
|
|
|
@@ -446,6 +463,13 @@
|
|
const pj_sockaddr_t *peer,
|
|
unsigned addr_len);
|
|
|
|
+/**
|
|
+ * RFC 6062
|
|
+ */
|
|
+PJ_DECL(pj_status_t) pj_turn_connect_peer(pj_turn_sock *sock,
|
|
+ pj_uint32_t conn_id,
|
|
+ const pj_sockaddr_t *peer_addr,
|
|
+ unsigned addr_len);
|
|
|
|
/**
|
|
* @}
|
|
--- a/pjnath/src/pjnath/stun_msg.c
|
|
+++ b/pjnath/src/pjnath/stun_msg.c
|
|
@@ -45,6 +45,9 @@
|
|
"Data", /* 7 */
|
|
"CreatePermission", /* 8 */
|
|
"ChannelBind", /* 9 */
|
|
+ "Connect", /* 10 */
|
|
+ "ConnectionBind", /* 11 */
|
|
+ "ConnectionAttempt", /* 12 */
|
|
};
|
|
|
|
static struct
|
|
@@ -476,11 +479,11 @@
|
|
NULL
|
|
},
|
|
{
|
|
- /* 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
|
|
},
|
|
{
|
|
/* ID 0x002b is not assigned */
|
|
--- a/pjnath/src/pjnath/stun_session.c
|
|
+++ b/pjnath/src/pjnath/stun_session.c
|
|
@@ -1511,3 +1511,14 @@
|
|
return status;
|
|
}
|
|
|
|
+PJ_DEF(void) pj_stun_session_get_server_cred(pj_stun_session *sess, pj_pool_t *pool, pj_str_t *nonce, pj_str_t *realm)
|
|
+{
|
|
+ pj_strdup(pool, nonce, &sess->next_nonce);
|
|
+ pj_strdup(pool, realm, &sess->server_realm);
|
|
+}
|
|
+
|
|
+PJ_DEF(void) pj_stun_session_set_server_cred(pj_stun_session *sess, const pj_str_t *nonce, pj_str_t *realm)
|
|
+{
|
|
+ pj_strdup(sess->pool, &sess->next_nonce, nonce);
|
|
+ pj_strdup(sess->pool, &sess->server_realm, realm);
|
|
+}
|
|
--- a/pjnath/src/pjnath/turn_session.c
|
|
+++ b/pjnath/src/pjnath/turn_session.c
|
|
@@ -42,6 +42,7 @@
|
|
"Resolving",
|
|
"Resolved",
|
|
"Allocating",
|
|
+ "TcpBinding",
|
|
"Ready",
|
|
"Deallocating",
|
|
"Deallocated",
|
|
@@ -208,6 +209,7 @@
|
|
PJ_DEF(void) pj_turn_alloc_param_default(pj_turn_alloc_param *prm)
|
|
{
|
|
pj_bzero(prm, sizeof(*prm));
|
|
+ prm->peer_conn_type = PJ_TURN_TP_UDP;
|
|
}
|
|
|
|
/*
|
|
@@ -403,6 +405,11 @@
|
|
sess->pending_destroy = PJ_TRUE;
|
|
can_destroy = PJ_FALSE;
|
|
break;
|
|
+ case PJ_TURN_STATE_CONNECTION_BINDING:
|
|
+ /* We need to wait until connection binding complete */
|
|
+ sess->pending_destroy = PJ_TRUE;
|
|
+ can_destroy = PJ_FALSE;
|
|
+ break;
|
|
case PJ_TURN_STATE_READY:
|
|
/* Send REFRESH with LIFETIME=0 */
|
|
can_destroy = PJ_FALSE;
|
|
@@ -719,6 +726,9 @@
|
|
PJ_ASSERT_RETURN(sess->state>PJ_TURN_STATE_NULL &&
|
|
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 */
|
|
if (param && param->af) {
|
|
@@ -756,7 +766,7 @@
|
|
/* MUST include REQUESTED-TRANSPORT attribute */
|
|
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 */
|
|
if (sess->alloc_param.bandwidth > 0) {
|
|
@@ -994,6 +1004,13 @@
|
|
}
|
|
}
|
|
|
|
+ /* rfc6062: direct send if peer connection is TCP */
|
|
+ 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),
|
|
PJ_FALSE, PJ_FALSE);
|
|
@@ -1670,6 +1687,33 @@
|
|
|
|
sess = (pj_turn_session*)pj_stun_session_get_user_data(stun);
|
|
|
|
+ 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_peer_connection) {
|
|
+ (*sess->cb.on_peer_connection)(sess, connection_id_attr->value,
|
|
+ &peer_attr->sockaddr,
|
|
+ pj_sockaddr_get_len(&peer_attr->sockaddr),
|
|
+ PJ_SUCCESS);
|
|
+ }
|
|
+ return PJ_SUCCESS;
|
|
+ }
|
|
+
|
|
/* Expecting Data Indication only */
|
|
if (msg->hdr.type != PJ_STUN_DATA_INDICATION) {
|
|
PJ_LOG(4,(sess->obj_name, "Unexpected STUN %s indication",
|
|
@@ -2089,3 +2133,16 @@
|
|
pj_grp_lock_release(sess->grp_lock);
|
|
}
|
|
|
|
+PJ_DEF(void) pj_turn_session_get_server_cred(pj_turn_session *sess,
|
|
+ pj_pool_t *pool, pj_str_t *nonce,
|
|
+ pj_str_t *realm)
|
|
+{
|
|
+ pj_stun_session_get_server_cred(sess->stun, pool, nonce, realm);
|
|
+}
|
|
+
|
|
+PJ_DEF(void) pj_turn_session_set_server_cred(pj_turn_session *sess,
|
|
+ const pj_str_t *nonce,
|
|
+ pj_str_t *realm)
|
|
+{
|
|
+ pj_stun_session_set_server_cred(sess->stun, nonce, realm);
|
|
+}
|
|
--- a/pjnath/src/pjnath/turn_sock.c
|
|
+++ b/pjnath/src/pjnath/turn_sock.c
|
|
@@ -35,9 +35,29 @@
|
|
|
|
enum { MAX_BIND_RETRY = 100 };
|
|
|
|
+enum { CONNECTION_USED = (1<<0), /* TCP connection slot is used or free */
|
|
+ CONNECTION_READY = (1<<1) /* TCP connection bind and ready to use for data transfer */
|
|
+};
|
|
|
|
#define INIT 0x1FFFFFFF
|
|
|
|
+/**
|
|
+ * pj_turn_tcp_data_connection contains information on TCP connection open between
|
|
+ * the client and the turn server, conveying data from/to a specific peer.
|
|
+ * notes: part of RFC 6062 support
|
|
+ */
|
|
+typedef struct pj_turn_tcp_data_connection
|
|
+{
|
|
+ pj_uint32_t id; /* identity of this connection as given by the TURN server */
|
|
+ pj_uint32_t flags; /* 0 or CONNECTION_USED */
|
|
+ pj_sockaddr peer_addr; /* mapped address of connected peer */
|
|
+ unsigned peer_addr_len;
|
|
+ pj_activesock_t *active_tcp_sock; /* socket between client and TURN server */
|
|
+ pj_ioqueue_op_key_t send_key;
|
|
+ pj_stun_session *stun_sess; /* STUN session used to send ConnectBind msg */
|
|
+ pj_turn_sock *turn_sock; /* up link */
|
|
+} pj_turn_tcp_data_connection;
|
|
+
|
|
struct pj_turn_sock
|
|
{
|
|
pj_pool_t *pool;
|
|
@@ -59,6 +79,11 @@
|
|
pj_turn_tp_type conn_type;
|
|
pj_activesock_t *active_sock;
|
|
pj_ioqueue_op_key_t send_key;
|
|
+
|
|
+ /* RFC 6062 */
|
|
+ pj_stun_auth_cred cred; /* saved from control connection */
|
|
+ pj_size_t tcp_cnx_count; /* number of elements in tcp_cnx */
|
|
+ pj_turn_tcp_data_connection tcp_cnx[PJ_TURN_MAX_TCP_CNX]; /* peer dedicated data connections throught the TURN server */
|
|
};
|
|
|
|
|
|
@@ -82,6 +107,12 @@
|
|
static void turn_on_state(pj_turn_session *sess,
|
|
pj_turn_state_t old_state,
|
|
pj_turn_state_t new_state);
|
|
+static void turn_on_peer_connection(pj_turn_session *sess,
|
|
+ pj_uint32_t conn_id,
|
|
+ const pj_sockaddr_t *peer_addr,
|
|
+ unsigned addr_len,
|
|
+ pj_status_t status);
|
|
+
|
|
|
|
static pj_bool_t on_data_read(pj_activesock_t *asock,
|
|
void *data,
|
|
@@ -97,6 +127,26 @@
|
|
static void destroy(pj_turn_sock *turn_sock);
|
|
static void timer_cb(pj_timer_heap_t *th, pj_timer_entry *e);
|
|
|
|
+static pj_bool_t on_peer_data_read(pj_activesock_t *asock,
|
|
+ void *data,
|
|
+ pj_size_t size,
|
|
+ pj_status_t status,
|
|
+ pj_size_t *remainder);
|
|
+static pj_bool_t on_peer_connect_complete(pj_activesock_t *asock,
|
|
+ pj_status_t status);
|
|
+static pj_status_t on_tcp_stun_send_msg(pj_stun_session *sess,
|
|
+ void *token,
|
|
+ const void *pkt,
|
|
+ pj_size_t pkt_size,
|
|
+ const pj_sockaddr_t *dst_addr,
|
|
+ unsigned addr_len);
|
|
+static void on_tcp_stun_request_complete(pj_stun_session *sess,
|
|
+ pj_status_t status,
|
|
+ void *token,
|
|
+ pj_stun_tx_data *tdata,
|
|
+ const pj_stun_msg *response,
|
|
+ const pj_sockaddr_t *src_addr,
|
|
+ unsigned src_addr_len);
|
|
|
|
/* Init config */
|
|
PJ_DEF(void) pj_turn_sock_cfg_default(pj_turn_sock_cfg *cfg)
|
|
@@ -193,6 +243,7 @@
|
|
sess_cb.on_channel_bound = &turn_on_channel_bound;
|
|
sess_cb.on_rx_data = &turn_on_rx_data;
|
|
sess_cb.on_state = &turn_on_state;
|
|
+ sess_cb.on_peer_connection = &turn_on_peer_connection;
|
|
status = pj_turn_session_create(cfg, pool->obj_name, af, conn_type,
|
|
turn_sock->grp_lock, &sess_cb, 0,
|
|
turn_sock, &turn_sock->sess);
|
|
@@ -238,6 +289,9 @@
|
|
pj_turn_session_shutdown(turn_sock->sess);
|
|
if (turn_sock->active_sock)
|
|
pj_activesock_close(turn_sock->active_sock);
|
|
+ for (int i=0; i < turn_sock->tcp_cnx_count; ++i) {
|
|
+ pj_activesock_close(turn_sock->tcp_cnx[i].active_tcp_sock);
|
|
+ }
|
|
pj_grp_lock_dec_ref(turn_sock->grp_lock);
|
|
pj_grp_lock_release(turn_sock->grp_lock);
|
|
}
|
|
@@ -302,6 +356,34 @@
|
|
}
|
|
}
|
|
|
|
+static void peer_cnx_fail(pj_turn_tcp_data_connection *tcp_cnx,
|
|
+ const char *title, pj_status_t status)
|
|
+{
|
|
+ pj_turn_sock *turn_sock = tcp_cnx->turn_sock;
|
|
+
|
|
+ show_err(turn_sock, title, status);
|
|
+ if (tcp_cnx->stun_sess) {
|
|
+ pj_stun_session_destroy(tcp_cnx->stun_sess);
|
|
+ tcp_cnx->stun_sess = NULL;
|
|
+ }
|
|
+ if (tcp_cnx->active_tcp_sock) {
|
|
+ for (int i=0; i < turn_sock->tcp_cnx_count; ++i) {
|
|
+ if (&turn_sock->tcp_cnx[i] == tcp_cnx) {
|
|
+ turn_on_peer_connection(turn_sock->sess, tcp_cnx->id,
|
|
+ &tcp_cnx->peer_addr,
|
|
+ tcp_cnx->peer_addr_len,
|
|
+ PJ_EEOF);
|
|
+ pj_activesock_close(tcp_cnx->active_tcp_sock);
|
|
+ tcp_cnx->active_tcp_sock = NULL;
|
|
+ tcp_cnx->flags = 0;
|
|
+ if (i == turn_sock->tcp_cnx_count-1)
|
|
+ --turn_sock->tcp_cnx_count;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
/*
|
|
* Set user data.
|
|
*/
|
|
@@ -411,6 +489,9 @@
|
|
|
|
/* Set credental */
|
|
if (cred) {
|
|
+ // save credentials for peer/TCP connections
|
|
+ if (param->peer_conn_type == PJ_TURN_TP_TCP)
|
|
+ pj_memcpy(&turn_sock->cred, cred, sizeof(turn_sock->cred));
|
|
status = pj_turn_session_set_credential(turn_sock->sess, cred);
|
|
if (status != PJ_SUCCESS) {
|
|
sess_fail(turn_sock, "Error setting credential", status);
|
|
@@ -676,11 +757,35 @@
|
|
return PJ_EINVALIDOP;
|
|
}
|
|
|
|
- PJ_UNUSED_ARG(dst_addr);
|
|
- PJ_UNUSED_ARG(dst_addr_len);
|
|
+ /* With TCP peer connection filter by address
|
|
+ * if packet is for the server or the peer
|
|
+ */
|
|
+ pj_activesock_t *asock = NULL;
|
|
+ pj_turn_session_info info;
|
|
+ pj_turn_session_get_info(turn_sock->sess, &info);
|
|
+ if (pj_sockaddr_cmp(&info.server, dst_addr) &&
|
|
+ turn_sock->alloc_param.peer_conn_type == PJ_TURN_TP_TCP) {
|
|
+ for (int i=0; i < turn_sock->tcp_cnx_count; ++i) {
|
|
+ pj_turn_tcp_data_connection *tcp_cnx = &turn_sock->tcp_cnx[i];
|
|
+ if ((tcp_cnx->flags & CONNECTION_READY) == 0)
|
|
+ continue;
|
|
+ if (!pj_sockaddr_cmp(&tcp_cnx->peer_addr, dst_addr)) {
|
|
+ asock = tcp_cnx->active_tcp_sock;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ if (!asock) {
|
|
+ status = PJ_ENOTFOUND;
|
|
+ show_err(turn_sock, "socket send()", status);
|
|
+ return status;
|
|
+ }
|
|
+ } else {
|
|
+ asock = turn_sock->active_sock;
|
|
+ }
|
|
|
|
- status = pj_activesock_send(turn_sock->active_sock, &turn_sock->send_key,
|
|
+ status = pj_activesock_send(asock, &turn_sock->send_key,
|
|
pkt, &len, 0);
|
|
+
|
|
if (status != PJ_SUCCESS && status != PJ_EPENDING) {
|
|
show_err(turn_sock, "socket send()", status);
|
|
}
|
|
@@ -927,4 +1032,365 @@
|
|
}
|
|
}
|
|
|
|
+static void turn_on_peer_connection(pj_turn_session *sess,
|
|
+ pj_uint32_t conn_id,
|
|
+ const pj_sockaddr_t *peer_addr,
|
|
+ unsigned addr_len,
|
|
+ pj_status_t status)
|
|
+{
|
|
+ pj_turn_sock *turn_sock = (pj_turn_sock*) pj_turn_session_get_user_data(sess);
|
|
+ if (turn_sock == NULL || turn_sock->is_destroying) {
|
|
+ /* We've been destroyed */
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (turn_sock->cb.on_peer_connection) {
|
|
+ (*turn_sock->cb.on_peer_connection)(turn_sock, conn_id,
|
|
+ peer_addr, addr_len,
|
|
+ status);
|
|
+ }
|
|
+}
|
|
+
|
|
+PJ_DECL(pj_status_t) pj_turn_connect_peer(pj_turn_sock *turn_sock,
|
|
+ pj_uint32_t conn_id,
|
|
+ const pj_sockaddr_t *peer_addr,
|
|
+ unsigned addr_len)
|
|
+{
|
|
+ pj_status_t status;
|
|
+ pj_turn_tcp_data_connection *new_tcp_cnx = NULL;
|
|
+
|
|
+ for (int i=0; i < turn_sock->tcp_cnx_count; ++i) {
|
|
+ pj_turn_tcp_data_connection *tcp_cnx = &turn_sock->tcp_cnx[i];
|
|
+ if ((tcp_cnx->flags & CONNECTION_USED) == 0) {
|
|
+ new_tcp_cnx = tcp_cnx;
|
|
+ continue;
|
|
+ }
|
|
+ if (tcp_cnx->id == conn_id)
|
|
+ // TODO: need log
|
|
+ return PJ_EINVAL; // TODO: need better error code
|
|
+ }
|
|
+
|
|
+ if (!new_tcp_cnx) {
|
|
+ if (turn_sock->tcp_cnx_count == PJ_TURN_MAX_TCP_CNX) {
|
|
+ // TODO: need log
|
|
+ return PJ_ETOOMANY;
|
|
+ }
|
|
+ new_tcp_cnx = &turn_sock->tcp_cnx[turn_sock->tcp_cnx_count++];
|
|
+ }
|
|
+
|
|
+ /* Initialize this TCP connection slot */
|
|
+ pj_bzero(new_tcp_cnx, sizeof(*new_tcp_cnx));
|
|
+ new_tcp_cnx->id = conn_id;
|
|
+ new_tcp_cnx->flags = CONNECTION_USED;
|
|
+ new_tcp_cnx->turn_sock = turn_sock;
|
|
+ pj_sockaddr_cp(&new_tcp_cnx->peer_addr, peer_addr);
|
|
+ new_tcp_cnx->peer_addr_len = addr_len;
|
|
+
|
|
+ pj_ioqueue_op_key_init(&new_tcp_cnx->send_key,
|
|
+ sizeof(new_tcp_cnx->send_key));
|
|
+
|
|
+ /* Initiate a new TCP connection on TURN server
|
|
+ * that will become the peer data connection */
|
|
+ pj_turn_session_info info;
|
|
+ int sock_type;
|
|
+ pj_sock_t sock;
|
|
+ pj_activesock_cfg asock_cfg;
|
|
+ pj_activesock_cb asock_cb;
|
|
+ pj_sockaddr bound_addr, *cfg_bind_addr;
|
|
+ pj_uint16_t max_bind_retry;
|
|
+
|
|
+ /* Get server address from session info */
|
|
+ pj_turn_session_get_info(turn_sock->sess, &info);
|
|
+
|
|
+ assert(turn_sock->conn_type == PJ_TURN_TP_TCP);
|
|
+ sock_type = pj_SOCK_STREAM();
|
|
+
|
|
+ /* Init socket */
|
|
+ status = pj_sock_socket(turn_sock->af, sock_type, 0, &sock);
|
|
+ if (status != PJ_SUCCESS) {
|
|
+ pj_turn_sock_destroy(turn_sock);
|
|
+ return status;
|
|
+ }
|
|
+
|
|
+ /* 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) {
|
|
+ pj_turn_sock_destroy(turn_sock);
|
|
+ return status;
|
|
+ }
|
|
+
|
|
+ /* 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 = &on_peer_data_read;
|
|
+ asock_cb.on_connect_complete = &on_peer_connect_complete;
|
|
+ status = pj_activesock_create(turn_sock->pool, sock,
|
|
+ sock_type, &asock_cfg,
|
|
+ turn_sock->cfg.ioqueue, &asock_cb,
|
|
+ new_tcp_cnx,
|
|
+ &new_tcp_cnx->active_tcp_sock);
|
|
+ if (status != PJ_SUCCESS) {
|
|
+ pj_turn_sock_destroy(turn_sock);
|
|
+ return status;
|
|
+ }
|
|
+
|
|
+ char addrtxt[PJ_INET6_ADDRSTRLEN+8];
|
|
+ PJ_LOG(5,(turn_sock->pool->obj_name,
|
|
+ "Connecting to %s",
|
|
+ pj_sockaddr_print(&info.server, addrtxt,
|
|
+ sizeof(addrtxt), 3)));
|
|
+
|
|
+ status = pj_activesock_start_connect(new_tcp_cnx->active_tcp_sock,
|
|
+ turn_sock->pool,
|
|
+ &info.server,
|
|
+ pj_sockaddr_get_len(&info.server));
|
|
+ if (status == PJ_SUCCESS) {
|
|
+ on_peer_connect_complete(new_tcp_cnx->active_tcp_sock, PJ_SUCCESS);
|
|
+ } else if (status != PJ_EPENDING) {
|
|
+ pj_perror(3, turn_sock->pool->obj_name, status,
|
|
+ "Failed to connect to %s",
|
|
+ pj_sockaddr_print(&info.server, addrtxt,
|
|
+ sizeof(addrtxt), 3));
|
|
+ pj_turn_sock_destroy(turn_sock);
|
|
+ return status;
|
|
+ }
|
|
+
|
|
+ return PJ_SUCCESS;
|
|
+}
|
|
+
|
|
+static pj_bool_t on_peer_data_read(pj_activesock_t *asock,
|
|
+ void *data,
|
|
+ pj_size_t size,
|
|
+ pj_status_t status,
|
|
+ pj_size_t *remainder)
|
|
+{
|
|
+ pj_turn_tcp_data_connection *tcp_cnx;
|
|
+ pj_turn_sock *turn_sock;
|
|
+
|
|
+ tcp_cnx = (pj_turn_tcp_data_connection*) pj_activesock_get_user_data(asock);
|
|
+ pj_assert(tcp_cnx && tcp_cnx->turn_sock);
|
|
+ turn_sock = tcp_cnx->turn_sock;
|
|
+
|
|
+ pj_grp_lock_acquire(turn_sock->grp_lock);
|
|
+
|
|
+ if (status != PJ_SUCCESS) {
|
|
+ peer_cnx_fail(tcp_cnx, "Peer connection closed", status);
|
|
+ pj_grp_lock_release(turn_sock->grp_lock);
|
|
+ return PJ_FALSE;
|
|
+ }
|
|
+
|
|
+ *remainder = size;
|
|
+ pj_uint8_t* pkt = data;
|
|
+ while (*remainder > 0) {
|
|
+ if ((tcp_cnx->flags & CONNECTION_READY) != 0) {
|
|
+ if (turn_sock->cb.on_rx_data)
|
|
+ turn_sock->cb.on_rx_data(turn_sock, pkt, *remainder,
|
|
+ &tcp_cnx->peer_addr,
|
|
+ tcp_cnx->peer_addr_len);
|
|
+ pj_grp_lock_release(turn_sock->grp_lock);
|
|
+ *remainder = 0;
|
|
+ return PJ_TRUE;
|
|
+ }
|
|
|
|
+ /* STUN session waiting for ConnectBind response */
|
|
+ pj_size_t parsed_len;
|
|
+ unsigned options = PJ_STUN_CHECK_PACKET | PJ_STUN_NO_FINGERPRINT_CHECK;
|
|
+ status = pj_stun_session_on_rx_pkt(tcp_cnx->stun_sess, pkt, size,
|
|
+ options, NULL, &parsed_len,
|
|
+ &tcp_cnx->peer_addr,
|
|
+ tcp_cnx->peer_addr_len);
|
|
+
|
|
+ if (status != PJ_SUCCESS) {
|
|
+ peer_cnx_fail(tcp_cnx, "Peer connection closed", status);
|
|
+ pj_grp_lock_release(turn_sock->grp_lock);
|
|
+ return PJ_FALSE;
|
|
+ }
|
|
+
|
|
+ PJ_LOG(3, ("rfc6062",
|
|
+ "parsed STUN msg (read %zu byte(s) over %zu), status=%u",
|
|
+ parsed_len, size, status));
|
|
+
|
|
+ pkt += parsed_len;
|
|
+ *remainder -= parsed_len;
|
|
+ }
|
|
+
|
|
+ pj_grp_lock_release(turn_sock->grp_lock);
|
|
+
|
|
+ return PJ_TRUE;
|
|
+}
|
|
+
|
|
+static pj_bool_t on_peer_connect_complete(pj_activesock_t *asock,
|
|
+ pj_status_t status)
|
|
+{
|
|
+ pj_turn_tcp_data_connection *tcp_cnx;
|
|
+ pj_turn_sock *turn_sock;
|
|
+
|
|
+ PJ_LOG(1, ("rfc6062", "peer data connection %s", status == PJ_SUCCESS ? "ready" : "failed"));
|
|
+
|
|
+ tcp_cnx = (pj_turn_tcp_data_connection*) pj_activesock_get_user_data(asock);
|
|
+ if (!tcp_cnx)
|
|
+ return PJ_FALSE;
|
|
+
|
|
+ turn_sock = tcp_cnx->turn_sock;
|
|
+ pj_assert(turn_sock);
|
|
+
|
|
+ pj_grp_lock_acquire(turn_sock->grp_lock);
|
|
+
|
|
+ PJ_LOG(3, ("rfc6062", "peer data connection %s", status == PJ_SUCCESS ? "ready" : "failed"));
|
|
+
|
|
+ // TODO: handle failures
|
|
+ if (status != PJ_SUCCESS) {
|
|
+ pj_grp_lock_release(turn_sock->grp_lock);
|
|
+ return PJ_FALSE;
|
|
+ }
|
|
+
|
|
+ /* start pending read operation */
|
|
+ status = pj_activesock_start_read(asock, turn_sock->pool,
|
|
+ turn_sock->setting.max_pkt_size, 0);
|
|
+ if (status != PJ_SUCCESS) {
|
|
+ // TODO: error handling
|
|
+ pj_grp_lock_release(turn_sock->grp_lock);
|
|
+ return PJ_FALSE;
|
|
+ }
|
|
+
|
|
+ /* Create a temporary STUN session to send the ConnectBind request */
|
|
+ pj_stun_session_cb stun_cb;
|
|
+ pj_bzero(&stun_cb, sizeof(stun_cb));
|
|
+ stun_cb.on_send_msg = &on_tcp_stun_send_msg;
|
|
+ stun_cb.on_request_complete = &on_tcp_stun_request_complete;
|
|
+ status = pj_stun_session_create(&turn_sock->cfg, NULL,
|
|
+ &stun_cb, PJ_FALSE, NULL,
|
|
+ &tcp_cnx->stun_sess);
|
|
+ if (status != PJ_SUCCESS) {
|
|
+ pj_grp_lock_release(turn_sock->grp_lock);
|
|
+ return PJ_FALSE;
|
|
+ }
|
|
+
|
|
+ pj_stun_session_set_user_data(tcp_cnx->stun_sess, tcp_cnx);
|
|
+
|
|
+ /* Copy credentials from control connection */
|
|
+ pj_stun_session_set_credential(tcp_cnx->stun_sess, PJ_STUN_AUTH_LONG_TERM, &turn_sock->cred);
|
|
+ pj_str_t server_nonce, server_realm;
|
|
+ pj_turn_session_get_server_cred(turn_sock->sess, turn_sock->pool, &server_nonce, &server_realm);
|
|
+ pj_stun_session_set_server_cred(tcp_cnx->stun_sess, &server_nonce, &server_realm);
|
|
+
|
|
+ /* Send ConnectBind request */
|
|
+ pj_stun_tx_data *tdata;
|
|
+ status = pj_stun_session_create_req(tcp_cnx->stun_sess, PJ_STUN_CONNECTION_BIND_REQUEST,
|
|
+ PJ_STUN_MAGIC, NULL, &tdata);
|
|
+ if (status != PJ_SUCCESS) {
|
|
+ pj_stun_session_destroy(tcp_cnx->stun_sess);
|
|
+ pj_grp_lock_release(turn_sock->grp_lock);
|
|
+ return PJ_FALSE;
|
|
+ }
|
|
+
|
|
+ /* MUST include REQUESTED-TRANSPORT attribute */
|
|
+ pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_CONNECTION_ID, tcp_cnx->id);
|
|
+
|
|
+ PJ_LOG(3, ("rfc6062", "bind TCP connection id=%x", tcp_cnx->id));
|
|
+ status = pj_stun_session_send_msg(tcp_cnx->stun_sess, tcp_cnx, PJ_FALSE, PJ_FALSE,
|
|
+ &tcp_cnx->peer_addr, tcp_cnx->peer_addr_len,
|
|
+ tdata);
|
|
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
|
|
+ pj_stun_session_destroy(tcp_cnx->stun_sess);
|
|
+ pj_grp_lock_release(turn_sock->grp_lock);
|
|
+ return status;
|
|
+ }
|
|
+
|
|
+ pj_grp_lock_release(turn_sock->grp_lock);
|
|
+ return PJ_TRUE;
|
|
+}
|
|
+
|
|
+static pj_status_t on_tcp_stun_send_msg(pj_stun_session *sess,
|
|
+ void *token,
|
|
+ const void *pkt,
|
|
+ pj_size_t pkt_size,
|
|
+ const pj_sockaddr_t *dst_addr,
|
|
+ unsigned addr_len)
|
|
+{
|
|
+ pj_status_t status;
|
|
+ pj_turn_tcp_data_connection *tcp_cnx = (pj_turn_tcp_data_connection*) token;
|
|
+ pj_assert(tcp_cnx != NULL && tcp_cnx->turn_sock != NULL);
|
|
+
|
|
+ pj_grp_lock_acquire(tcp_cnx->turn_sock->grp_lock);
|
|
+ status = pj_activesock_send(tcp_cnx->active_tcp_sock, &tcp_cnx->send_key, pkt, &pkt_size, 0);
|
|
+ pj_grp_lock_release(tcp_cnx->turn_sock->grp_lock);
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static void on_tcp_stun_request_complete(pj_stun_session *sess,
|
|
+ pj_status_t status,
|
|
+ void *token,
|
|
+ pj_stun_tx_data *tdata,
|
|
+ const pj_stun_msg *response,
|
|
+ const pj_sockaddr_t *src_addr,
|
|
+ unsigned src_addr_len)
|
|
+{
|
|
+ pj_turn_tcp_data_connection *tcp_cnx = (pj_turn_tcp_data_connection*) token;
|
|
+ pj_assert(tcp_cnx != NULL && tcp_cnx->turn_sock != NULL);
|
|
+
|
|
+ pj_grp_lock_acquire(tcp_cnx->turn_sock->grp_lock);
|
|
+ pj_stun_session_destroy(tcp_cnx->stun_sess);
|
|
+ tcp_cnx->stun_sess = NULL;
|
|
+ tcp_cnx->flags |= CONNECTION_READY;
|
|
+ PJ_LOG(3, ("rfc6062", "peer data connection bind %s", status == PJ_SUCCESS ? "succeed" : "failed"));
|
|
+ pj_grp_lock_release(tcp_cnx->turn_sock->grp_lock);
|
|
+}
|
|
|