
With this patch we ensure that local socket errors during connection establishment are properly transmitted to the connection check layer, so the related pair can be put in state failed when needed. The local socket errors we are interested in, are those occuring when the local source address cannot be bound anymore, because the underlying interface vanished for example. In particular, we don't ignore errors coming from g_socket_bind() with tcp active sockets, that perform a bind to *whatever* local address is available, in case it cannot bind to the specified address. This behaviour was demonstrated with the test-tcp example, that tried to bind to the IPv4 loopback address on a socket initially created for the IPv6 loopback.
2289 lines
69 KiB
C
2289 lines
69 KiB
C
/*
|
||
* This file is part of the Nice GLib ICE library.
|
||
*
|
||
* (C) 2008 Collabora Ltd.
|
||
* Contact: Youness Alaoui
|
||
* (C) 2008 Nokia Corporation
|
||
*
|
||
* The contents of this file are subject to the Mozilla Public License Version
|
||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||
* the License. You may obtain a copy of the License at
|
||
* http://www.mozilla.org/MPL/
|
||
*
|
||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||
* for the specific language governing rights and limitations under the
|
||
* License.
|
||
*
|
||
* The Original Code is the Nice GLib ICE library.
|
||
*
|
||
* The Initial Developers of the Original Code are Collabora Ltd and Nokia
|
||
* Corporation. All Rights Reserved.
|
||
*
|
||
* Contributors:
|
||
* Youness Alaoui, Collabora Ltd.
|
||
*
|
||
* Alternatively, the contents of this file may be used under the terms of the
|
||
* the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which
|
||
* case the provisions of LGPL are applicable instead of those above. If you
|
||
* wish to allow use of your version of this file only under the terms of the
|
||
* LGPL and not to allow others to use your version of this file under the
|
||
* MPL, indicate your decision by deleting the provisions above and replace
|
||
* them with the notice and other provisions required by the LGPL. If you do
|
||
* not delete the provisions above, a recipient may use your version of this
|
||
* file under either the MPL or the LGPL.
|
||
*/
|
||
|
||
/*
|
||
* Implementation of TURN
|
||
*/
|
||
#ifdef HAVE_CONFIG_H
|
||
# include "config.h"
|
||
#endif
|
||
|
||
#include <string.h>
|
||
#include <errno.h>
|
||
#include <fcntl.h>
|
||
#include <stdlib.h>
|
||
|
||
#include "udp-turn.h"
|
||
#include "stun/stunagent.h"
|
||
#include "stun/usages/timer.h"
|
||
#include "agent-priv.h"
|
||
|
||
#define STUN_END_TIMEOUT 8000
|
||
#define STUN_MAX_MS_REALM_LEN 128 // as defined in [MS-TURN]
|
||
#define STUN_EXPIRE_TIMEOUT 60 /* Time we refresh before expiration */
|
||
#define STUN_PERMISSION_TIMEOUT (300 - STUN_EXPIRE_TIMEOUT) /* 240 s */
|
||
#define STUN_BINDING_TIMEOUT (600 - STUN_EXPIRE_TIMEOUT) /* 540 s */
|
||
|
||
static GMutex mutex;
|
||
|
||
typedef struct {
|
||
StunMessage message;
|
||
uint8_t buffer[STUN_MAX_MESSAGE_SIZE];
|
||
StunTimer timer;
|
||
} TURNMessage;
|
||
|
||
typedef struct {
|
||
NiceAddress peer;
|
||
uint16_t channel;
|
||
gboolean renew;
|
||
GSource *timeout_source;
|
||
} ChannelBinding;
|
||
|
||
typedef struct {
|
||
GMainContext *ctx;
|
||
StunAgent agent;
|
||
GList *channels;
|
||
GList *pending_bindings;
|
||
ChannelBinding *current_binding;
|
||
TURNMessage *current_binding_msg;
|
||
GList *pending_permissions;
|
||
GSource *tick_source_channel_bind;
|
||
GSource *tick_source_create_permission;
|
||
NiceSocket *base_socket;
|
||
NiceAddress server_addr;
|
||
uint8_t *username;
|
||
gsize username_len;
|
||
uint8_t *password;
|
||
gsize password_len;
|
||
NiceTurnSocketCompatibility compatibility;
|
||
GQueue *send_requests;
|
||
uint8_t ms_realm[STUN_MAX_MS_REALM_LEN + 1];
|
||
uint8_t ms_connection_id[20];
|
||
uint32_t ms_sequence_num;
|
||
bool ms_connection_id_valid;
|
||
GList *permissions; /* the peers (NiceAddress) for which
|
||
there is an installed permission */
|
||
GList *sent_permissions; /* ongoing permission installed */
|
||
GHashTable *send_data_queues; /* stores a send data queue for per peer */
|
||
GSource *permission_timeout_source; /* timer used to invalidate
|
||
permissions */
|
||
|
||
guint8 *cached_realm;
|
||
uint16_t cached_realm_len;
|
||
guint8 *cached_nonce;
|
||
uint16_t cached_nonce_len;
|
||
|
||
GByteArray *fragment_buffer;
|
||
NiceAddress from;
|
||
uint8_t *send_buffer;
|
||
} UdpTurnPriv;
|
||
|
||
|
||
typedef struct {
|
||
StunTransactionId id;
|
||
GSource *source;
|
||
UdpTurnPriv *priv;
|
||
} SendRequest;
|
||
|
||
/* used to store data sent while obtaining a permission */
|
||
typedef struct {
|
||
gchar *data;
|
||
guint data_len;
|
||
gboolean reliable;
|
||
} SendData;
|
||
|
||
static void socket_close (NiceSocket *sock);
|
||
static gint socket_recv_messages (NiceSocket *sock,
|
||
NiceInputMessage *recv_messages, guint n_recv_messages);
|
||
static gint socket_send_messages (NiceSocket *sock, const NiceAddress *to,
|
||
const NiceOutputMessage *messages, guint n_messages);
|
||
static gint socket_send_messages_reliable (NiceSocket *sock,
|
||
const NiceAddress *to, const NiceOutputMessage *messages, guint n_messages);
|
||
static gboolean socket_is_reliable (NiceSocket *sock);
|
||
static gboolean socket_can_send (NiceSocket *sock, NiceAddress *addr);
|
||
static void socket_set_writable_callback (NiceSocket *sock,
|
||
NiceSocketWritableCb callback, gpointer user_data);
|
||
static gboolean socket_is_based_on (NiceSocket *sock, NiceSocket *other);
|
||
|
||
static void priv_process_pending_bindings (UdpTurnPriv *priv);
|
||
static gboolean priv_retransmissions_tick_unlocked (UdpTurnPriv *priv);
|
||
static gboolean priv_retransmissions_tick (gpointer pointer);
|
||
static void priv_schedule_tick (UdpTurnPriv *priv);
|
||
static void priv_send_turn_message (UdpTurnPriv *priv, TURNMessage *msg);
|
||
static gboolean priv_send_create_permission (UdpTurnPriv *priv,
|
||
const NiceAddress *peer);
|
||
static gboolean priv_send_channel_bind (UdpTurnPriv *priv,
|
||
uint16_t channel,
|
||
const NiceAddress *peer);
|
||
static gboolean priv_add_channel_binding (UdpTurnPriv *priv,
|
||
const NiceAddress *peer);
|
||
static gboolean priv_forget_send_request_timeout (gpointer pointer);
|
||
static void priv_clear_permissions (UdpTurnPriv *priv);
|
||
|
||
static void
|
||
send_request_free (SendRequest *r)
|
||
{
|
||
g_source_destroy (r->source);
|
||
g_source_unref (r->source);
|
||
|
||
stun_agent_forget_transaction (&r->priv->agent, r->id);
|
||
|
||
g_slice_free (SendRequest, r);
|
||
}
|
||
|
||
static guint
|
||
priv_nice_address_hash (gconstpointer data)
|
||
{
|
||
gchar address[NICE_ADDRESS_STRING_LEN];
|
||
|
||
nice_address_to_string ((NiceAddress *) data, address);
|
||
|
||
return g_str_hash(address);
|
||
}
|
||
|
||
static void
|
||
priv_send_data_queue_destroy (gpointer user_data)
|
||
{
|
||
GQueue *send_queue = (GQueue *) user_data;
|
||
GList *i;
|
||
|
||
for (i = g_queue_peek_head_link (send_queue); i; i = i->next) {
|
||
SendData *data = (SendData *) i->data;
|
||
|
||
g_free (data->data);
|
||
g_slice_free (SendData, data);
|
||
}
|
||
g_queue_free (send_queue);
|
||
}
|
||
|
||
NiceSocket *
|
||
nice_udp_turn_socket_new (GMainContext *ctx, NiceAddress *addr,
|
||
NiceSocket *base_socket, const NiceAddress *server_addr,
|
||
const gchar *username, const gchar *password,
|
||
NiceTurnSocketCompatibility compatibility)
|
||
{
|
||
UdpTurnPriv *priv;
|
||
NiceSocket *sock = g_slice_new0 (NiceSocket);
|
||
|
||
if (!sock) {
|
||
return NULL;
|
||
}
|
||
|
||
priv = g_new0 (UdpTurnPriv, 1);
|
||
|
||
if (compatibility == NICE_TURN_SOCKET_COMPATIBILITY_DRAFT9 ||
|
||
compatibility == NICE_TURN_SOCKET_COMPATIBILITY_RFC5766) {
|
||
stun_agent_init (&priv->agent, STUN_ALL_KNOWN_ATTRIBUTES,
|
||
STUN_COMPATIBILITY_RFC5389,
|
||
STUN_AGENT_USAGE_LONG_TERM_CREDENTIALS);
|
||
} else if (compatibility == NICE_TURN_SOCKET_COMPATIBILITY_MSN) {
|
||
stun_agent_init (&priv->agent, STUN_ALL_KNOWN_ATTRIBUTES,
|
||
STUN_COMPATIBILITY_RFC3489,
|
||
STUN_AGENT_USAGE_SHORT_TERM_CREDENTIALS |
|
||
STUN_AGENT_USAGE_NO_INDICATION_AUTH);
|
||
} else if (compatibility == NICE_TURN_SOCKET_COMPATIBILITY_GOOGLE) {
|
||
stun_agent_init (&priv->agent, STUN_ALL_KNOWN_ATTRIBUTES,
|
||
STUN_COMPATIBILITY_RFC3489,
|
||
STUN_AGENT_USAGE_SHORT_TERM_CREDENTIALS |
|
||
STUN_AGENT_USAGE_IGNORE_CREDENTIALS);
|
||
} else if (compatibility == NICE_TURN_SOCKET_COMPATIBILITY_OC2007) {
|
||
stun_agent_init (&priv->agent, STUN_ALL_KNOWN_ATTRIBUTES,
|
||
STUN_COMPATIBILITY_OC2007,
|
||
STUN_AGENT_USAGE_LONG_TERM_CREDENTIALS |
|
||
STUN_AGENT_USAGE_NO_ALIGNED_ATTRIBUTES);
|
||
}
|
||
|
||
priv->channels = NULL;
|
||
priv->current_binding = NULL;
|
||
priv->base_socket = base_socket;
|
||
if (ctx)
|
||
priv->ctx = g_main_context_ref (ctx);
|
||
|
||
if (compatibility == NICE_TURN_SOCKET_COMPATIBILITY_MSN ||
|
||
compatibility == NICE_TURN_SOCKET_COMPATIBILITY_OC2007) {
|
||
priv->username = g_base64_decode (username, &priv->username_len);
|
||
priv->password = g_base64_decode (password, &priv->password_len);
|
||
} else {
|
||
priv->username = (uint8_t *)g_strdup (username);
|
||
priv->username_len = (gsize) strlen (username);
|
||
if (compatibility == NICE_TURN_SOCKET_COMPATIBILITY_GOOGLE) {
|
||
priv->password = NULL;
|
||
priv->password_len = 0;
|
||
} else {
|
||
priv->password = (uint8_t *)g_strdup (password);
|
||
priv->password_len = (gsize) strlen (password);
|
||
}
|
||
}
|
||
priv->server_addr = *server_addr;
|
||
priv->compatibility = compatibility;
|
||
priv->send_requests = g_queue_new ();
|
||
|
||
priv->send_data_queues =
|
||
g_hash_table_new_full (priv_nice_address_hash,
|
||
(GEqualFunc) nice_address_equal,
|
||
(GDestroyNotify) nice_address_free,
|
||
priv_send_data_queue_destroy);
|
||
|
||
priv->send_buffer = g_malloc (STUN_MAX_MESSAGE_SIZE);
|
||
|
||
sock->type = NICE_SOCKET_TYPE_UDP_TURN;
|
||
sock->fileno = NULL;
|
||
sock->addr = *addr;
|
||
sock->send_messages = socket_send_messages;
|
||
sock->send_messages_reliable = socket_send_messages_reliable;
|
||
sock->recv_messages = socket_recv_messages;
|
||
sock->is_reliable = socket_is_reliable;
|
||
sock->can_send = socket_can_send;
|
||
sock->set_writable_callback = socket_set_writable_callback;
|
||
sock->is_based_on = socket_is_based_on;
|
||
sock->close = socket_close;
|
||
sock->priv = (void *) priv;
|
||
|
||
return sock;
|
||
}
|
||
|
||
|
||
|
||
static void
|
||
socket_close (NiceSocket *sock)
|
||
{
|
||
UdpTurnPriv *priv = (UdpTurnPriv *) sock->priv;
|
||
GList *i = NULL;
|
||
|
||
g_mutex_lock (&mutex);
|
||
|
||
for (i = priv->channels; i; i = i->next) {
|
||
ChannelBinding *b = i->data;
|
||
if (b->timeout_source) {
|
||
g_source_destroy (b->timeout_source);
|
||
g_source_unref (b->timeout_source);
|
||
}
|
||
g_free (b);
|
||
}
|
||
g_list_free (priv->channels);
|
||
|
||
g_list_free_full (priv->pending_bindings, (GDestroyNotify) nice_address_free);
|
||
|
||
if (priv->tick_source_channel_bind != NULL) {
|
||
g_source_destroy (priv->tick_source_channel_bind);
|
||
g_source_unref (priv->tick_source_channel_bind);
|
||
priv->tick_source_channel_bind = NULL;
|
||
}
|
||
|
||
if (priv->tick_source_create_permission != NULL) {
|
||
g_source_destroy (priv->tick_source_create_permission);
|
||
g_source_unref (priv->tick_source_create_permission);
|
||
priv->tick_source_create_permission = NULL;
|
||
}
|
||
|
||
g_queue_free_full (priv->send_requests, (GDestroyNotify) send_request_free);
|
||
|
||
priv_clear_permissions (priv);
|
||
g_list_free_full (priv->sent_permissions, (GDestroyNotify) nice_address_free);
|
||
g_hash_table_destroy (priv->send_data_queues);
|
||
|
||
if (priv->permission_timeout_source) {
|
||
g_source_destroy (priv->permission_timeout_source);
|
||
g_source_unref (priv->permission_timeout_source);
|
||
priv->permission_timeout_source = NULL;
|
||
}
|
||
|
||
if (priv->ctx)
|
||
g_main_context_unref (priv->ctx);
|
||
|
||
g_free (priv->current_binding);
|
||
g_free (priv->current_binding_msg);
|
||
g_list_free_full (priv->pending_permissions, g_free);
|
||
g_free (priv->username);
|
||
g_free (priv->password);
|
||
g_free (priv->cached_realm);
|
||
g_free (priv->cached_nonce);
|
||
|
||
if (priv->fragment_buffer) {
|
||
g_byte_array_free(priv->fragment_buffer, TRUE);
|
||
}
|
||
|
||
g_free (priv->send_buffer);
|
||
|
||
g_free (priv);
|
||
|
||
sock->priv = NULL;
|
||
|
||
g_mutex_unlock (&mutex);
|
||
}
|
||
|
||
static gint
|
||
socket_recv_messages (NiceSocket *sock,
|
||
NiceInputMessage *recv_messages, guint n_recv_messages)
|
||
{
|
||
UdpTurnPriv *priv = (UdpTurnPriv *) sock->priv;
|
||
gint n_messages;
|
||
gint n_output_messages = 0;
|
||
guint i;
|
||
gboolean error = FALSE;
|
||
|
||
/* Make sure socket has not been freed: */
|
||
g_assert (sock->priv != NULL);
|
||
|
||
nice_debug_verbose ("received message on TURN socket");
|
||
|
||
if (priv->fragment_buffer) {
|
||
/* Fill as many recv_messages as possible with RFC4571-framed data we
|
||
* already hold in our buffer before reading more from the base socket. */
|
||
guint8 *f_buffer = priv->fragment_buffer->data;
|
||
guint f_buffer_len = priv->fragment_buffer->len;
|
||
|
||
for (i = 0; i < n_recv_messages && f_buffer_len >= sizeof (guint16); ++i) {
|
||
guint32 msg_len = ((f_buffer[0] << 8) | f_buffer[1]) + sizeof (guint16);
|
||
|
||
if (msg_len > f_buffer_len) {
|
||
/* The next message in the buffer isn't complete yet. Wait for more
|
||
* data from the base socket. */
|
||
break;
|
||
}
|
||
|
||
/* We have a full message in the buffer. Copy it into the user-provided
|
||
* NiceInputMessage. */
|
||
memcpy_buffer_to_input_message (&recv_messages[i], f_buffer, msg_len);
|
||
*recv_messages[i].from = priv->from;
|
||
|
||
f_buffer += msg_len;
|
||
f_buffer_len -= msg_len;
|
||
++n_output_messages;
|
||
}
|
||
|
||
/* Adjust recv_messages with the number of messages we've just filled. */
|
||
recv_messages += n_output_messages;
|
||
n_recv_messages -= n_output_messages;
|
||
|
||
/* Shrink the fragment buffer, deallocate it if empty. */
|
||
g_byte_array_remove_range (priv->fragment_buffer, 0,
|
||
priv->fragment_buffer->len - f_buffer_len);
|
||
if (priv->fragment_buffer->len == 0) {
|
||
g_byte_array_free (priv->fragment_buffer, TRUE);
|
||
priv->fragment_buffer = NULL;
|
||
}
|
||
}
|
||
|
||
n_messages = nice_socket_recv_messages (priv->base_socket,
|
||
recv_messages, n_recv_messages);
|
||
|
||
if (n_messages < 0)
|
||
return n_messages;
|
||
|
||
/* Process all the messages. Those which fail parsing are re-used for the next
|
||
* message.
|
||
*
|
||
* FIXME: This needs a fast path which avoids allocations or memcpy()s.
|
||
* Implementing such a path means rewriting the TURN parser (and hence the
|
||
* STUN message code) to operate on vectors of buffers, rather than a
|
||
* monolithic buffer. */
|
||
for (i = 0; i < (guint) n_messages; ++i) {
|
||
NiceInputMessage *message = &recv_messages[i];
|
||
NiceSocket *dummy;
|
||
NiceAddress from;
|
||
guint8 *buffer;
|
||
gsize buffer_length;
|
||
gint parsed_buffer_length;
|
||
gboolean allocated_buffer = FALSE;
|
||
|
||
if (message->length == 0)
|
||
continue;
|
||
|
||
/* Compact the message’s buffers into a single one for parsing. Avoid this
|
||
* in the (hopefully) common case of a single-element buffer vector. */
|
||
if (message->n_buffers == 1 ||
|
||
(message->n_buffers == -1 &&
|
||
message->buffers[0].buffer != NULL &&
|
||
message->buffers[1].buffer == NULL)) {
|
||
buffer = message->buffers[0].buffer;
|
||
buffer_length = message->length;
|
||
} else {
|
||
nice_debug_verbose ("%s: **WARNING: SLOW PATH**", G_STRFUNC);
|
||
|
||
buffer = compact_input_message (message, &buffer_length);
|
||
allocated_buffer = TRUE;
|
||
}
|
||
|
||
/* Parse in-place. */
|
||
parsed_buffer_length = nice_udp_turn_socket_parse_recv (sock, &dummy,
|
||
&from, buffer_length, buffer,
|
||
message->from, buffer, buffer_length);
|
||
message->length = MAX (parsed_buffer_length, 0);
|
||
|
||
if (parsed_buffer_length < 0) {
|
||
error = TRUE;
|
||
} else if (parsed_buffer_length > 0) {
|
||
*message->from = from;
|
||
}
|
||
/* parsed_buffer_length == 0 means this is a TURN control message which
|
||
* needs ignoring. */
|
||
|
||
if (nice_socket_is_reliable (sock) && parsed_buffer_length > 0) {
|
||
/* Determine the portion of the current NiceInputMessage we can already
|
||
* return. */
|
||
gint32 msg_len = 0;
|
||
if (!priv->fragment_buffer) {
|
||
msg_len = ((buffer[0] << 8) | buffer[1]) + sizeof (guint16);
|
||
if (msg_len > parsed_buffer_length) {
|
||
/* The RFC4571 frame is larger than the current TURN message, need to
|
||
* buffer it and wait for more data. */
|
||
msg_len = 0;
|
||
}
|
||
}
|
||
|
||
if (msg_len != parsed_buffer_length && !priv->fragment_buffer) {
|
||
/* Start of message fragmenting detected. Allocate fragment buffer
|
||
* large enough for the recv_message's we haven't parsed yet. */
|
||
gint j;
|
||
guint buffer_len = 0;
|
||
|
||
for (j = i; j < n_messages; ++j) {
|
||
buffer_len += recv_messages[j].length;
|
||
}
|
||
priv->fragment_buffer = g_byte_array_sized_new (buffer_len);
|
||
}
|
||
|
||
if (priv->fragment_buffer) {
|
||
/* The messages are fragmented. Store the excess data (after msg_len
|
||
* bytes) into fragment buffer for reassembly. */
|
||
g_byte_array_append (priv->fragment_buffer, buffer + msg_len,
|
||
parsed_buffer_length - msg_len);
|
||
|
||
parsed_buffer_length = msg_len;
|
||
message->length = msg_len;
|
||
priv->from = from;
|
||
}
|
||
}
|
||
|
||
/* Split up the monolithic buffer again into the caller-provided buffers. */
|
||
if (parsed_buffer_length > 0 && allocated_buffer) {
|
||
memcpy_buffer_to_input_message (message, buffer,
|
||
parsed_buffer_length);
|
||
}
|
||
|
||
if (allocated_buffer)
|
||
g_free (buffer);
|
||
|
||
if (error)
|
||
break;
|
||
|
||
++n_output_messages;
|
||
}
|
||
|
||
/* Was there an error processing the first message? */
|
||
if (error && i == 0)
|
||
return -1;
|
||
|
||
return n_output_messages;
|
||
}
|
||
|
||
/* interval is given in milliseconds */
|
||
static GSource *
|
||
priv_timeout_add_with_context (UdpTurnPriv *priv, guint interval,
|
||
GSourceFunc function, gpointer data)
|
||
{
|
||
GSource *source = NULL;
|
||
|
||
g_return_val_if_fail (function != NULL, NULL);
|
||
|
||
source = g_timeout_source_new (interval);
|
||
|
||
g_source_set_callback (source, function, data, NULL);
|
||
g_source_attach (source, priv->ctx);
|
||
|
||
return source;
|
||
}
|
||
|
||
/* interval is given in seconds */
|
||
static GSource *
|
||
priv_timeout_add_seconds_with_context (UdpTurnPriv *priv, guint interval,
|
||
GSourceFunc function, gpointer data)
|
||
{
|
||
GSource *source = NULL;
|
||
|
||
g_return_val_if_fail (function != NULL, NULL);
|
||
|
||
source = g_timeout_source_new_seconds (interval);
|
||
|
||
g_source_set_callback (source, function, data, NULL);
|
||
g_source_attach (source, priv->ctx);
|
||
|
||
return source;
|
||
}
|
||
|
||
static StunMessageReturn
|
||
stun_message_append_ms_connection_id(StunMessage *msg,
|
||
uint8_t *ms_connection_id, uint32_t ms_sequence_num)
|
||
{
|
||
union {
|
||
uint8_t buf8[24];
|
||
uint32_t buf32[24/4];
|
||
} buf;
|
||
|
||
memcpy(buf.buf8, ms_connection_id, 20);
|
||
buf.buf32[5] = htonl(ms_sequence_num);
|
||
return stun_message_append_bytes (msg, STUN_ATTRIBUTE_MS_SEQUENCE_NUMBER,
|
||
buf.buf8, 24);
|
||
}
|
||
|
||
static void
|
||
stun_message_ensure_ms_realm(StunMessage *msg, uint8_t *realm)
|
||
{
|
||
/* With MS-TURN, original clients do not send REALM attribute in Send and Set
|
||
* Active Destination requests, but use it to compute MESSAGE-INTEGRITY. We
|
||
* simply append cached realm value to the message and use it in subsequent
|
||
* stun_agent_finish_message() call. Messages with this additional attribute
|
||
* are handled correctly on OCS Access Edge working as TURN server. */
|
||
if (stun_message_get_method(msg) == STUN_SEND ||
|
||
stun_message_get_method(msg) == STUN_OLD_SET_ACTIVE_DST) {
|
||
stun_message_append_bytes (msg, STUN_ATTRIBUTE_REALM, realm,
|
||
strlen((char *)realm));
|
||
}
|
||
}
|
||
|
||
static gboolean
|
||
priv_is_peer_in_list (const GList *list, const NiceAddress *peer)
|
||
{
|
||
const GList *iter;
|
||
|
||
for (iter = list ; iter ; iter = g_list_next (iter)) {
|
||
NiceAddress *address = (NiceAddress *) iter->data;
|
||
|
||
if (nice_address_equal (address, peer))
|
||
return TRUE;
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static gboolean
|
||
priv_has_permission_for_peer (UdpTurnPriv *priv, const NiceAddress *peer)
|
||
{
|
||
return priv_is_peer_in_list (priv->permissions, peer);
|
||
}
|
||
|
||
static gboolean
|
||
priv_has_sent_permission_for_peer (UdpTurnPriv *priv, const NiceAddress *peer)
|
||
{
|
||
return priv_is_peer_in_list (priv->sent_permissions, peer);
|
||
}
|
||
|
||
static void
|
||
priv_add_permission_for_peer (UdpTurnPriv *priv, const NiceAddress *peer)
|
||
{
|
||
priv->permissions =
|
||
g_list_append (priv->permissions, nice_address_dup (peer));
|
||
}
|
||
|
||
static void
|
||
priv_add_sent_permission_for_peer (UdpTurnPriv *priv, const NiceAddress *peer)
|
||
{
|
||
priv->sent_permissions =
|
||
g_list_append (priv->sent_permissions, nice_address_dup (peer));
|
||
}
|
||
|
||
static GList *
|
||
priv_remove_peer_from_list (GList *list, const NiceAddress *peer)
|
||
{
|
||
GList *iter;
|
||
|
||
for (iter = list ; iter ; iter = g_list_next (iter)) {
|
||
NiceAddress *address = (NiceAddress *) iter->data;
|
||
|
||
if (nice_address_equal (address, peer)) {
|
||
GList *prev = iter->prev;
|
||
|
||
nice_address_free (address);
|
||
list = g_list_delete_link (list, iter);
|
||
iter = prev;
|
||
if (iter)
|
||
iter = list;
|
||
}
|
||
}
|
||
|
||
return list;
|
||
}
|
||
|
||
static void
|
||
priv_remove_sent_permission_for_peer (UdpTurnPriv *priv, const NiceAddress *peer)
|
||
{
|
||
priv->sent_permissions =
|
||
priv_remove_peer_from_list (priv->sent_permissions, peer);
|
||
}
|
||
|
||
static void
|
||
priv_clear_permissions (UdpTurnPriv *priv)
|
||
{
|
||
g_list_free_full (priv->permissions, (GDestroyNotify) nice_address_free);
|
||
priv->permissions = NULL;
|
||
}
|
||
|
||
static gint
|
||
_socket_send_messages_wrapped (NiceSocket *sock, const NiceAddress *to,
|
||
const NiceOutputMessage *messages, guint n_messages, gboolean reliable)
|
||
{
|
||
if (!nice_socket_is_reliable (sock)) {
|
||
if (reliable)
|
||
return nice_socket_send_messages_reliable (sock, to, messages, n_messages);
|
||
else
|
||
return nice_socket_send_messages (sock, to, messages, n_messages);
|
||
} else {
|
||
GOutputVector *local_bufs;
|
||
NiceOutputMessage local_message;
|
||
const NiceOutputMessage *message;
|
||
gsize message_len;
|
||
guint n_bufs = 0;
|
||
guint16 rfc4571_frame;
|
||
guint i;
|
||
gint ret;
|
||
|
||
g_assert (n_messages == 1);
|
||
message = &messages[0];
|
||
message_len = output_message_get_size (message);
|
||
g_assert (message_len <= G_MAXUINT16);
|
||
|
||
/* ICE-TCP requires that all packets be framed with RFC4571 */
|
||
|
||
/* Count the number of buffers. */
|
||
if (message->n_buffers == -1) {
|
||
for (i = 0; message->buffers[i].buffer != NULL; i++)
|
||
n_bufs++;
|
||
} else {
|
||
n_bufs = message->n_buffers;
|
||
}
|
||
|
||
local_bufs = g_alloca ((n_bufs + 1) * sizeof (GOutputVector));
|
||
local_message.buffers = local_bufs;
|
||
local_message.n_buffers = n_bufs + 1;
|
||
|
||
rfc4571_frame = htons (message_len);
|
||
local_bufs[0].buffer = &rfc4571_frame;
|
||
local_bufs[0].size = sizeof (guint16);
|
||
|
||
for (i = 0; i < n_bufs; i++) {
|
||
local_bufs[i + 1].buffer = message->buffers[i].buffer;
|
||
local_bufs[i + 1].size = message->buffers[i].size;
|
||
}
|
||
|
||
|
||
if (reliable)
|
||
ret = nice_socket_send_messages_reliable (sock, to,
|
||
&local_message, 1);
|
||
else
|
||
ret = nice_socket_send_messages (sock, to, &local_message, 1);
|
||
|
||
if (ret == 1)
|
||
ret = message_len;
|
||
|
||
return ret;
|
||
}
|
||
}
|
||
|
||
static gssize
|
||
_socket_send_wrapped (NiceSocket *sock, const NiceAddress *to,
|
||
guint len, const gchar *buf, gboolean reliable)
|
||
{
|
||
gint ret;
|
||
|
||
if (!nice_socket_is_reliable (sock)) {
|
||
GOutputVector local_buf = { buf, len };
|
||
NiceOutputMessage local_message = { &local_buf, 1};
|
||
|
||
ret = _socket_send_messages_wrapped (sock, to, &local_message, 1, reliable);
|
||
if (ret == 1)
|
||
return len;
|
||
return ret;
|
||
} else {
|
||
guint16 rfc4571_frame = htons (len);
|
||
GOutputVector local_buf[2] = {{&rfc4571_frame, 2}, { buf, len }};
|
||
NiceOutputMessage local_message = { local_buf, 2};
|
||
|
||
if (reliable)
|
||
ret = nice_socket_send_messages_reliable (sock, to, &local_message, 1);
|
||
else
|
||
ret = nice_socket_send_messages (sock, to, &local_message, 1);
|
||
|
||
if (ret == 1)
|
||
return len;
|
||
return ret;
|
||
}
|
||
}
|
||
|
||
static void
|
||
socket_enqueue_data(UdpTurnPriv *priv, const NiceAddress *to,
|
||
guint len, const gchar *buf, gboolean reliable)
|
||
{
|
||
SendData *data = g_slice_new0 (SendData);
|
||
GQueue *queue = g_hash_table_lookup (priv->send_data_queues, to);
|
||
|
||
if (queue == NULL) {
|
||
queue = g_queue_new ();
|
||
g_hash_table_insert (priv->send_data_queues, nice_address_dup (to),
|
||
queue);
|
||
}
|
||
|
||
data->data = g_memdup(buf, len);
|
||
data->data_len = len;
|
||
data->reliable = reliable;
|
||
g_queue_push_tail (queue, data);
|
||
}
|
||
|
||
static void
|
||
socket_dequeue_all_data (UdpTurnPriv *priv, const NiceAddress *to)
|
||
{
|
||
GQueue *send_queue = g_hash_table_lookup (priv->send_data_queues, to);
|
||
|
||
if (send_queue) {
|
||
while (!g_queue_is_empty (send_queue)) {
|
||
SendData *data =
|
||
(SendData *) g_queue_pop_head(send_queue);
|
||
|
||
nice_debug_verbose ("dequeuing data");
|
||
_socket_send_wrapped (priv->base_socket, &priv->server_addr,
|
||
data->data_len, data->data, data->reliable);
|
||
|
||
g_free (data->data);
|
||
g_slice_free (SendData, data);
|
||
}
|
||
|
||
/* remove queue from table */
|
||
g_hash_table_remove (priv->send_data_queues, to);
|
||
}
|
||
}
|
||
|
||
|
||
static gssize
|
||
socket_send_message (NiceSocket *sock, const NiceAddress *to,
|
||
const NiceOutputMessage *message, gboolean reliable)
|
||
{
|
||
UdpTurnPriv *priv = (UdpTurnPriv *) sock->priv;
|
||
StunMessage msg;
|
||
uint8_t *buffer;
|
||
size_t msg_len;
|
||
union {
|
||
struct sockaddr_storage storage;
|
||
struct sockaddr addr;
|
||
} sa;
|
||
GList *i;
|
||
ChannelBinding *binding = NULL;
|
||
gint ret;
|
||
|
||
/* Make sure socket has not been freed: */
|
||
g_assert (sock->priv != NULL);
|
||
|
||
buffer = priv->send_buffer;
|
||
|
||
for (i = priv->channels; i; i = i->next) {
|
||
ChannelBinding *b = i->data;
|
||
if (nice_address_equal (&b->peer, to)) {
|
||
binding = b;
|
||
break;
|
||
}
|
||
}
|
||
|
||
nice_address_copy_to_sockaddr (to, &sa.addr);
|
||
|
||
if (binding) {
|
||
if (priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_DRAFT9 ||
|
||
priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_RFC5766) {
|
||
gsize message_len = output_message_get_size (message);
|
||
|
||
if (message_len + sizeof(uint32_t) <= STUN_MAX_MESSAGE_SIZE) {
|
||
guint j;
|
||
uint16_t len16, channel16;
|
||
gsize message_offset = 0;
|
||
|
||
len16 = htons ((uint16_t) message_len);
|
||
channel16 = htons (binding->channel);
|
||
|
||
memcpy (buffer, &channel16, sizeof(uint16_t));
|
||
memcpy (buffer + sizeof(uint16_t), &len16, sizeof(uint16_t));
|
||
|
||
/* FIXME: Slow path! This should be replaced by code which manipulates
|
||
* the GOutputVector array, rather than the buffer contents
|
||
* themselves. */
|
||
for (j = 0;
|
||
(message->n_buffers >= 0 && j < (guint) message->n_buffers) ||
|
||
(message->n_buffers < 0 && message->buffers[j].buffer != NULL);
|
||
j++) {
|
||
const GOutputVector *out_buf = &message->buffers[j];
|
||
gsize out_len;
|
||
|
||
out_len = MIN (message_len - message_offset, out_buf->size);
|
||
memcpy (buffer + sizeof (uint32_t) + message_offset,
|
||
out_buf->buffer, out_len);
|
||
message_offset += out_len;
|
||
}
|
||
|
||
msg_len = message_len + sizeof(uint32_t);
|
||
} else {
|
||
goto error;
|
||
}
|
||
} else {
|
||
ret = _socket_send_messages_wrapped (priv->base_socket,
|
||
&priv->server_addr, message, 1, reliable);
|
||
|
||
if (ret == 1)
|
||
return output_message_get_size (message);
|
||
return ret;
|
||
}
|
||
} else {
|
||
guint8 *compacted_buf;
|
||
gsize compacted_buf_len;
|
||
|
||
if (priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_DRAFT9 ||
|
||
priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_RFC5766) {
|
||
if (!stun_agent_init_indication (&priv->agent, &msg,
|
||
buffer, STUN_MAX_MESSAGE_SIZE, STUN_IND_SEND))
|
||
goto error;
|
||
if (stun_message_append_xor_addr (&msg, STUN_ATTRIBUTE_PEER_ADDRESS,
|
||
&sa.storage, sizeof(sa)) !=
|
||
STUN_MESSAGE_RETURN_SUCCESS)
|
||
goto error;
|
||
} else {
|
||
if (!stun_agent_init_request (&priv->agent, &msg,
|
||
buffer, STUN_MAX_MESSAGE_SIZE, STUN_SEND))
|
||
goto error;
|
||
|
||
if (stun_message_append32 (&msg, STUN_ATTRIBUTE_MAGIC_COOKIE,
|
||
TURN_MAGIC_COOKIE) != STUN_MESSAGE_RETURN_SUCCESS)
|
||
goto error;
|
||
if (priv->username != NULL && priv->username_len > 0) {
|
||
if (stun_message_append_bytes (&msg, STUN_ATTRIBUTE_USERNAME,
|
||
priv->username, priv->username_len) !=
|
||
STUN_MESSAGE_RETURN_SUCCESS)
|
||
goto error;
|
||
}
|
||
if (stun_message_append_addr (&msg, STUN_ATTRIBUTE_DESTINATION_ADDRESS,
|
||
&sa.addr, sizeof(sa)) !=
|
||
STUN_MESSAGE_RETURN_SUCCESS)
|
||
goto error;
|
||
|
||
if (priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_GOOGLE &&
|
||
priv->current_binding &&
|
||
nice_address_equal (&priv->current_binding->peer, to)) {
|
||
if (stun_message_append32 (&msg, STUN_ATTRIBUTE_OPTIONS, 1) !=
|
||
STUN_MESSAGE_RETURN_SUCCESS)
|
||
goto error;
|
||
}
|
||
}
|
||
|
||
if (priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_OC2007) {
|
||
if(stun_message_append32(&msg, STUN_ATTRIBUTE_MS_VERSION, 1) !=
|
||
STUN_MESSAGE_RETURN_SUCCESS)
|
||
goto error;
|
||
|
||
if (priv->ms_connection_id_valid)
|
||
if (stun_message_append_ms_connection_id(&msg, priv->ms_connection_id,
|
||
++priv->ms_sequence_num) !=
|
||
STUN_MESSAGE_RETURN_SUCCESS)
|
||
goto error;
|
||
|
||
stun_message_ensure_ms_realm(&msg, priv->ms_realm);
|
||
}
|
||
|
||
/* Slow path! We have to compact the buffers to append them to the message.
|
||
* FIXME: This could be improved by adding vectored I/O support to
|
||
* stun_message_append_bytes(). */
|
||
compacted_buf = compact_output_message (message, &compacted_buf_len);
|
||
|
||
if (stun_message_append_bytes (&msg, STUN_ATTRIBUTE_DATA,
|
||
compacted_buf, compacted_buf_len) != STUN_MESSAGE_RETURN_SUCCESS) {
|
||
g_free (compacted_buf);
|
||
goto error;
|
||
}
|
||
|
||
g_free (compacted_buf);
|
||
|
||
/* Finish the message. */
|
||
msg_len = stun_agent_finish_message (&priv->agent, &msg,
|
||
priv->password, priv->password_len);
|
||
if (msg_len > 0 && stun_message_get_class (&msg) == STUN_REQUEST &&
|
||
priv->compatibility != NICE_TURN_SOCKET_COMPATIBILITY_OC2007) {
|
||
SendRequest *req = g_slice_new0 (SendRequest);
|
||
|
||
req->priv = priv;
|
||
stun_message_id (&msg, req->id);
|
||
req->source = priv_timeout_add_with_context (priv,
|
||
STUN_END_TIMEOUT, priv_forget_send_request_timeout, req);
|
||
g_queue_push_tail (priv->send_requests, req);
|
||
}
|
||
}
|
||
|
||
if (msg_len > 0) {
|
||
if (priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_RFC5766 &&
|
||
!priv_has_permission_for_peer (priv, to)) {
|
||
if (!priv_has_sent_permission_for_peer (priv, to)) {
|
||
if (!priv_send_create_permission (priv, to))
|
||
goto error;
|
||
}
|
||
|
||
/* enque data */
|
||
nice_debug_verbose ("enqueuing data");
|
||
socket_enqueue_data(priv, to, msg_len, (gchar *)buffer, reliable);
|
||
|
||
return msg_len;
|
||
} else {
|
||
GOutputVector local_buf = { buffer, msg_len };
|
||
NiceOutputMessage local_message = {&local_buf, 1};
|
||
|
||
ret = _socket_send_messages_wrapped (priv->base_socket,
|
||
&priv->server_addr, &local_message, 1, reliable);
|
||
|
||
if (ret == 1)
|
||
return msg_len;
|
||
return ret;
|
||
}
|
||
}
|
||
|
||
/* Error condition pass through to the base socket. */
|
||
ret = _socket_send_messages_wrapped (priv->base_socket, to, message, 1,
|
||
reliable);
|
||
if (ret == 1)
|
||
return output_message_get_size (message);
|
||
return ret;
|
||
error:
|
||
return -1;
|
||
}
|
||
|
||
static gint
|
||
socket_send_messages (NiceSocket *sock, const NiceAddress *to,
|
||
const NiceOutputMessage *messages, guint n_messages)
|
||
{
|
||
guint i;
|
||
|
||
g_mutex_lock (&mutex);
|
||
|
||
/* Make sure socket has not been freed: */
|
||
g_assert (sock->priv != NULL);
|
||
|
||
for (i = 0; i < n_messages; i++) {
|
||
const NiceOutputMessage *message = &messages[i];
|
||
gssize len;
|
||
|
||
len = socket_send_message (sock, to, message, FALSE);
|
||
|
||
if (len < 0) {
|
||
/* Error. */
|
||
if (i > 0)
|
||
break;
|
||
g_mutex_unlock (&mutex);
|
||
return len;
|
||
} else if (len == 0) {
|
||
/* EWOULDBLOCK. */
|
||
break;
|
||
}
|
||
}
|
||
|
||
g_mutex_unlock (&mutex);
|
||
|
||
return i;
|
||
}
|
||
|
||
static gint
|
||
socket_send_messages_reliable (NiceSocket *sock, const NiceAddress *to,
|
||
const NiceOutputMessage *messages, guint n_messages)
|
||
{
|
||
UdpTurnPriv *priv = (UdpTurnPriv *) sock->priv;
|
||
guint i;
|
||
|
||
g_mutex_lock (&mutex);
|
||
|
||
/* TURN can depend either on tcp-turn or udp-bsd as a base socket
|
||
* if we allow reliable send and need to create permissions and we queue the
|
||
* data, then we must be sure that the reliable send will succeed later, so
|
||
* we check for udp-bsd here as the base socket and don't allow it.
|
||
*/
|
||
if (priv->base_socket->type == NICE_SOCKET_TYPE_UDP_BSD) {
|
||
g_mutex_unlock (&mutex);
|
||
return -1;
|
||
}
|
||
|
||
for (i = 0; i < n_messages; i++) {
|
||
const NiceOutputMessage *message = &messages[i];
|
||
gssize len;
|
||
|
||
len = socket_send_message (sock, to, message, TRUE);
|
||
|
||
if (len < 0) {
|
||
/* Error. */
|
||
g_mutex_unlock (&mutex);
|
||
return len;
|
||
} else if (len == 0) {
|
||
/* EWOULDBLOCK. */
|
||
break;
|
||
}
|
||
}
|
||
|
||
g_mutex_unlock (&mutex);
|
||
return i;
|
||
}
|
||
|
||
static gboolean
|
||
socket_is_reliable (NiceSocket *sock)
|
||
{
|
||
UdpTurnPriv *priv = (UdpTurnPriv *) sock->priv;
|
||
|
||
return nice_socket_is_reliable (priv->base_socket);
|
||
}
|
||
|
||
static gboolean
|
||
socket_can_send (NiceSocket *sock, NiceAddress *addr)
|
||
{
|
||
UdpTurnPriv *priv = sock->priv;
|
||
|
||
return nice_socket_can_send (priv->base_socket, addr);
|
||
}
|
||
|
||
static void
|
||
socket_set_writable_callback (NiceSocket *sock,
|
||
NiceSocketWritableCb callback, gpointer user_data)
|
||
{
|
||
UdpTurnPriv *priv = sock->priv;
|
||
|
||
nice_socket_set_writable_callback (priv->base_socket, callback, user_data);
|
||
}
|
||
|
||
static gboolean
|
||
socket_is_based_on (NiceSocket *sock, NiceSocket *other)
|
||
{
|
||
UdpTurnPriv *priv = sock->priv;
|
||
|
||
return (sock == other) ||
|
||
(priv && nice_socket_is_based_on (priv->base_socket, other));
|
||
}
|
||
|
||
static gboolean
|
||
priv_forget_send_request_timeout (gpointer pointer)
|
||
{
|
||
SendRequest *req = pointer;
|
||
|
||
g_mutex_lock (&mutex);
|
||
if (g_source_is_destroyed (g_main_current_source ())) {
|
||
nice_debug ("Source was destroyed. "
|
||
"Avoided race condition in turn.c:priv_forget_send_request");
|
||
g_mutex_unlock (&mutex);
|
||
return G_SOURCE_REMOVE;
|
||
}
|
||
|
||
g_queue_remove (req->priv->send_requests, req);
|
||
send_request_free (req);
|
||
|
||
g_mutex_unlock (&mutex);
|
||
|
||
return G_SOURCE_REMOVE;
|
||
}
|
||
|
||
static gboolean
|
||
priv_permission_timeout (gpointer data)
|
||
{
|
||
UdpTurnPriv *priv = (UdpTurnPriv *) data;
|
||
|
||
nice_debug ("Permission is about to timeout, schedule renewal");
|
||
|
||
g_mutex_lock (&mutex);
|
||
|
||
if (g_source_is_destroyed (g_main_current_source ())) {
|
||
nice_debug ("Source was destroyed. Avoided race condition in "
|
||
"udp-turn.c:priv_permission_timeout");
|
||
|
||
g_mutex_unlock (&mutex);
|
||
return G_SOURCE_REMOVE;
|
||
}
|
||
|
||
|
||
/* remove all permissions for this agent (the permission for the peer
|
||
we are sending to will be renewed) */
|
||
priv_clear_permissions (priv);
|
||
g_mutex_unlock (&mutex);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
priv_binding_expired_timeout (gpointer data)
|
||
{
|
||
UdpTurnPriv *priv = (UdpTurnPriv *) data;
|
||
GList *i;
|
||
GSource *source = NULL;
|
||
|
||
g_mutex_lock (&mutex);
|
||
if (g_source_is_destroyed (g_main_current_source ())) {
|
||
nice_debug ("Source was destroyed. Avoided race condition in "
|
||
"udp-turn.c:priv_permission_timeout");
|
||
|
||
g_mutex_unlock (&mutex);
|
||
return G_SOURCE_REMOVE;
|
||
}
|
||
|
||
nice_debug ("Permission expired, refresh failed");
|
||
|
||
/* find current binding and destroy it */
|
||
for (i = priv->channels ; i; i = i->next) {
|
||
ChannelBinding *b = i->data;
|
||
if (b->timeout_source == source) {
|
||
priv->channels = g_list_remove (priv->channels, b);
|
||
/* Make sure we don't free a currently being-refreshed binding */
|
||
if (priv->current_binding_msg && !priv->current_binding) {
|
||
union {
|
||
struct sockaddr_storage storage;
|
||
struct sockaddr addr;
|
||
} sa;
|
||
socklen_t sa_len = sizeof(sa);
|
||
NiceAddress to;
|
||
|
||
/* look up binding associated with peer */
|
||
stun_message_find_xor_addr (
|
||
&priv->current_binding_msg->message,
|
||
STUN_ATTRIBUTE_XOR_PEER_ADDRESS, &sa.storage, &sa_len);
|
||
nice_address_set_from_sockaddr (&to, &sa.addr);
|
||
|
||
/* If the binding is being refreshed, then move it to
|
||
priv->current_binding so it counts as a 'new' binding and
|
||
will get readded to the list if it succeeds */
|
||
if (nice_address_equal (&b->peer, &to)) {
|
||
priv->current_binding = b;
|
||
break;
|
||
}
|
||
}
|
||
/* In case the binding timed out before it could be processed, add it to
|
||
the pending list */
|
||
priv_add_channel_binding (priv, &b->peer);
|
||
g_free (b);
|
||
break;
|
||
}
|
||
}
|
||
|
||
g_mutex_unlock (&mutex);
|
||
return G_SOURCE_REMOVE;
|
||
}
|
||
|
||
static gboolean
|
||
priv_binding_timeout (gpointer data)
|
||
{
|
||
UdpTurnPriv *priv = (UdpTurnPriv *) data;
|
||
GList *i;
|
||
GSource *source = NULL;
|
||
|
||
g_mutex_lock (&mutex);
|
||
if (g_source_is_destroyed (g_main_current_source ())) {
|
||
nice_debug ("Source was destroyed. Avoided race condition in "
|
||
"udp-turn.c:priv_permission_timeout");
|
||
|
||
g_mutex_unlock (&mutex);
|
||
return G_SOURCE_REMOVE;
|
||
}
|
||
|
||
nice_debug ("Permission is about to timeout, sending binding renewal");
|
||
source = g_main_current_source ();
|
||
|
||
/* find current binding and mark it for renewal */
|
||
for (i = priv->channels ; i; i = i->next) {
|
||
ChannelBinding *b = i->data;
|
||
|
||
if (b->timeout_source == source) {
|
||
b->renew = TRUE;
|
||
|
||
/* Remove any existing timer */
|
||
if (b->timeout_source) {
|
||
g_source_destroy (b->timeout_source);
|
||
g_source_unref (b->timeout_source);
|
||
}
|
||
|
||
/* Install timer to expire the permission */
|
||
b->timeout_source = priv_timeout_add_seconds_with_context (priv,
|
||
STUN_EXPIRE_TIMEOUT, priv_binding_expired_timeout, priv);
|
||
|
||
/* Send renewal */
|
||
if (!priv->current_binding_msg)
|
||
priv_send_channel_bind (priv, b->channel, &b->peer);
|
||
break;
|
||
}
|
||
}
|
||
|
||
g_mutex_unlock (&mutex);
|
||
|
||
return G_SOURCE_REMOVE;
|
||
}
|
||
|
||
static void
|
||
nice_udp_turn_socket_cache_realm_nonce_locked (NiceSocket *sock,
|
||
StunMessage *msg)
|
||
{
|
||
UdpTurnPriv *priv = sock->priv;
|
||
gconstpointer tmp;
|
||
|
||
g_assert (sock->type == NICE_SOCKET_TYPE_UDP_TURN);
|
||
|
||
g_free (priv->cached_realm);
|
||
priv->cached_realm = NULL;
|
||
priv->cached_realm_len = 0;
|
||
|
||
g_free (priv->cached_nonce);
|
||
priv->cached_nonce = NULL;
|
||
priv->cached_nonce_len = 0;
|
||
|
||
tmp = stun_message_find (msg, STUN_ATTRIBUTE_REALM, &priv->cached_realm_len);
|
||
if (tmp && priv->cached_realm_len < 764)
|
||
priv->cached_realm = g_memdup (tmp, priv->cached_realm_len);
|
||
|
||
tmp = stun_message_find (msg, STUN_ATTRIBUTE_NONCE, &priv->cached_nonce_len);
|
||
if (tmp && priv->cached_nonce_len < 764)
|
||
priv->cached_nonce = g_memdup (tmp, priv->cached_nonce_len);
|
||
|
||
}
|
||
|
||
void
|
||
nice_udp_turn_socket_cache_realm_nonce (NiceSocket *sock,
|
||
StunMessage *msg)
|
||
{
|
||
g_mutex_lock (&mutex);
|
||
nice_udp_turn_socket_cache_realm_nonce_locked (sock, msg);
|
||
g_mutex_unlock (&mutex);
|
||
}
|
||
|
||
guint
|
||
nice_udp_turn_socket_parse_recv_message (NiceSocket *sock, NiceSocket **from_sock,
|
||
NiceInputMessage *message)
|
||
{
|
||
/* TODO: Speed this up in the common reliable case of having a 24-byte header
|
||
* buffer to begin with, followed by one or more massive buffers. */
|
||
guint8 *buf;
|
||
gsize buf_len, len;
|
||
|
||
if (message->n_buffers == 1 ||
|
||
(message->n_buffers == -1 &&
|
||
message->buffers[0].buffer != NULL &&
|
||
message->buffers[1].buffer == NULL)) {
|
||
/* Fast path. Single massive buffer. */
|
||
len = nice_udp_turn_socket_parse_recv (sock, from_sock,
|
||
message->from, message->length, message->buffers[0].buffer,
|
||
message->from, message->buffers[0].buffer, message->length);
|
||
|
||
g_assert (len <= message->length);
|
||
|
||
message->length = len;
|
||
|
||
return (len > 0) ? 1 : 0;
|
||
}
|
||
|
||
/* Slow path. */
|
||
nice_debug_verbose ("%s: **WARNING: SLOW PATH**", G_STRFUNC);
|
||
|
||
buf = compact_input_message (message, &buf_len);
|
||
len = nice_udp_turn_socket_parse_recv (sock, from_sock,
|
||
message->from, buf_len, buf,
|
||
message->from, buf, buf_len);
|
||
len = memcpy_buffer_to_input_message (message, buf, len);
|
||
g_free (buf);
|
||
|
||
return (len > 0) ? 1 : 0;
|
||
}
|
||
|
||
gsize
|
||
nice_udp_turn_socket_parse_recv (NiceSocket *sock, NiceSocket **from_sock,
|
||
NiceAddress *from, gsize len, guint8 *buf,
|
||
const NiceAddress *recv_from, const guint8 *_recv_buf, gsize recv_len)
|
||
{
|
||
|
||
UdpTurnPriv *priv = (UdpTurnPriv *) sock->priv;
|
||
StunValidationStatus valid;
|
||
StunMessage msg;
|
||
GList *l;
|
||
ChannelBinding *binding = NULL;
|
||
|
||
union {
|
||
const guint8 *u8;
|
||
const guint16 *u16;
|
||
} recv_buf;
|
||
|
||
g_mutex_lock (&mutex);
|
||
|
||
/* In the case of a reliable UDP-TURN-OVER-TCP (which means MS-TURN)
|
||
* we must use RFC4571 framing */
|
||
if (nice_socket_is_reliable (sock)) {
|
||
recv_buf.u8 = _recv_buf + sizeof(guint16);
|
||
recv_len -= sizeof(guint16);
|
||
} else {
|
||
recv_buf.u8 = _recv_buf;
|
||
}
|
||
|
||
if (nice_address_equal (&priv->server_addr, recv_from)) {
|
||
valid = stun_agent_validate (&priv->agent, &msg,
|
||
recv_buf.u8, recv_len, NULL, NULL);
|
||
|
||
if (valid == STUN_VALIDATION_SUCCESS) {
|
||
if (priv->compatibility != NICE_TURN_SOCKET_COMPATIBILITY_DRAFT9 &&
|
||
priv->compatibility != NICE_TURN_SOCKET_COMPATIBILITY_RFC5766) {
|
||
uint32_t cookie;
|
||
if (stun_message_find32 (&msg, STUN_ATTRIBUTE_MAGIC_COOKIE,
|
||
&cookie) != STUN_MESSAGE_RETURN_SUCCESS)
|
||
goto recv;
|
||
if (cookie != TURN_MAGIC_COOKIE)
|
||
goto recv;
|
||
}
|
||
|
||
if (stun_message_get_method (&msg) == STUN_SEND) {
|
||
if (stun_message_get_class (&msg) == STUN_RESPONSE) {
|
||
SendRequest *req = NULL;
|
||
GList *i = g_queue_peek_head_link (priv->send_requests);
|
||
StunTransactionId msg_id;
|
||
|
||
stun_message_id (&msg, msg_id);
|
||
|
||
for (; i; i = i->next) {
|
||
SendRequest *r = i->data;
|
||
if (memcmp (&r->id, msg_id, sizeof(StunTransactionId)) == 0) {
|
||
req = r;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (req) {
|
||
g_queue_remove (priv->send_requests, req);
|
||
send_request_free (req);
|
||
}
|
||
|
||
if (priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_GOOGLE) {
|
||
uint32_t opts = 0;
|
||
if (stun_message_find32 (&msg, STUN_ATTRIBUTE_OPTIONS, &opts) ==
|
||
STUN_MESSAGE_RETURN_SUCCESS && opts & 0x1)
|
||
goto msn_google_lock;
|
||
}
|
||
}
|
||
|
||
goto done;
|
||
} else if (stun_message_get_method (&msg) == STUN_OLD_SET_ACTIVE_DST) {
|
||
StunTransactionId request_id;
|
||
StunTransactionId response_id;
|
||
|
||
if (priv->current_binding && priv->current_binding_msg) {
|
||
stun_message_id (&msg, response_id);
|
||
stun_message_id (&priv->current_binding_msg->message, request_id);
|
||
if (memcmp (request_id, response_id,
|
||
sizeof(StunTransactionId)) == 0) {
|
||
g_free (priv->current_binding_msg);
|
||
priv->current_binding_msg = NULL;
|
||
|
||
if (stun_message_get_class (&msg) == STUN_RESPONSE &&
|
||
(priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_OC2007 ||
|
||
priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_MSN)) {
|
||
goto msn_google_lock;
|
||
} else {
|
||
g_free (priv->current_binding);
|
||
priv->current_binding = NULL;
|
||
}
|
||
}
|
||
}
|
||
|
||
goto done;
|
||
} else if (stun_message_get_method (&msg) == STUN_CHANNELBIND) {
|
||
StunTransactionId request_id;
|
||
StunTransactionId response_id;
|
||
|
||
if (priv->current_binding_msg) {
|
||
stun_message_id (&msg, response_id);
|
||
stun_message_id (&priv->current_binding_msg->message, request_id);
|
||
if (memcmp (request_id, response_id,
|
||
sizeof(StunTransactionId)) == 0) {
|
||
|
||
if (priv->current_binding) {
|
||
/* New channel binding */
|
||
binding = priv->current_binding;
|
||
} else {
|
||
/* Existing binding refresh */
|
||
GList *i;
|
||
union {
|
||
struct sockaddr_storage storage;
|
||
struct sockaddr addr;
|
||
} sa;
|
||
socklen_t sa_len = sizeof(sa);
|
||
NiceAddress to;
|
||
|
||
/* look up binding associated with peer */
|
||
stun_message_find_xor_addr (
|
||
&priv->current_binding_msg->message,
|
||
STUN_ATTRIBUTE_XOR_PEER_ADDRESS, &sa.storage, &sa_len);
|
||
nice_address_set_from_sockaddr (&to, &sa.addr);
|
||
|
||
for (i = priv->channels; i; i = i->next) {
|
||
ChannelBinding *b = i->data;
|
||
if (nice_address_equal (&b->peer, &to)) {
|
||
binding = b;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (stun_message_get_class (&msg) == STUN_ERROR) {
|
||
int code = -1;
|
||
uint8_t *sent_realm = NULL;
|
||
uint8_t *recv_realm = NULL;
|
||
uint16_t sent_realm_len = 0;
|
||
uint16_t recv_realm_len = 0;
|
||
|
||
sent_realm =
|
||
(uint8_t *) stun_message_find (
|
||
&priv->current_binding_msg->message,
|
||
STUN_ATTRIBUTE_REALM, &sent_realm_len);
|
||
recv_realm =
|
||
(uint8_t *) stun_message_find (&msg,
|
||
STUN_ATTRIBUTE_REALM, &recv_realm_len);
|
||
|
||
/* check for unauthorized error response */
|
||
if (stun_message_find_error (&msg, &code) ==
|
||
STUN_MESSAGE_RETURN_SUCCESS &&
|
||
(code == STUN_ERROR_STALE_NONCE ||
|
||
(code == STUN_ERROR_UNAUTHORIZED &&
|
||
!(recv_realm != NULL &&
|
||
recv_realm_len > 0 &&
|
||
recv_realm_len == sent_realm_len &&
|
||
sent_realm != NULL &&
|
||
memcmp (sent_realm, recv_realm,
|
||
sent_realm_len) == 0)))) {
|
||
|
||
g_free (priv->current_binding_msg);
|
||
priv->current_binding_msg = NULL;
|
||
nice_udp_turn_socket_cache_realm_nonce_locked (sock, &msg);
|
||
if (binding)
|
||
priv_send_channel_bind (priv, binding->channel,
|
||
&binding->peer);
|
||
} else {
|
||
g_free (priv->current_binding);
|
||
priv->current_binding = NULL;
|
||
g_free (priv->current_binding_msg);
|
||
priv->current_binding_msg = NULL;
|
||
priv_process_pending_bindings (priv);
|
||
}
|
||
} else if (stun_message_get_class (&msg) == STUN_RESPONSE) {
|
||
g_free (priv->current_binding_msg);
|
||
priv->current_binding_msg = NULL;
|
||
|
||
/* If it's a new channel binding, then add it to the list */
|
||
if (priv->current_binding)
|
||
priv->channels = g_list_append (priv->channels,
|
||
priv->current_binding);
|
||
priv->current_binding = NULL;
|
||
|
||
if (binding) {
|
||
binding->renew = FALSE;
|
||
|
||
/* Remove any existing timer */
|
||
if (binding->timeout_source) {
|
||
g_source_destroy (binding->timeout_source);
|
||
g_source_unref (binding->timeout_source);
|
||
}
|
||
/* Install timer to schedule refresh of the permission */
|
||
binding->timeout_source =
|
||
priv_timeout_add_seconds_with_context (priv,
|
||
STUN_BINDING_TIMEOUT, priv_binding_timeout, priv);
|
||
}
|
||
priv_process_pending_bindings (priv);
|
||
}
|
||
}
|
||
}
|
||
goto done;
|
||
} else if (stun_message_get_method (&msg) == STUN_CREATEPERMISSION) {
|
||
StunTransactionId request_id;
|
||
StunTransactionId response_id;
|
||
GList *i, *next;
|
||
TURNMessage *current_create_permission_msg;
|
||
|
||
for (i = priv->pending_permissions; i; i = next) {
|
||
current_create_permission_msg = (TURNMessage *) i->data;
|
||
next = i->next;
|
||
|
||
stun_message_id (&msg, response_id);
|
||
stun_message_id (¤t_create_permission_msg->message, request_id);
|
||
|
||
if (memcmp (request_id, response_id,
|
||
sizeof(StunTransactionId)) == 0) {
|
||
union {
|
||
struct sockaddr_storage storage;
|
||
struct sockaddr addr;
|
||
} peer;
|
||
socklen_t peer_len = sizeof(peer);
|
||
NiceAddress to;
|
||
gchar tmpbuf[INET6_ADDRSTRLEN];
|
||
|
||
stun_message_find_xor_addr (
|
||
¤t_create_permission_msg->message,
|
||
STUN_ATTRIBUTE_XOR_PEER_ADDRESS, &peer.storage, &peer_len);
|
||
nice_address_set_from_sockaddr (&to, &peer.addr);
|
||
nice_address_to_string (&to, tmpbuf);
|
||
nice_debug ("TURN: got response for CreatePermission "
|
||
"with XOR_PEER_ADDRESS=[%s]:%u : %s",
|
||
tmpbuf, nice_address_get_port (&to),
|
||
stun_message_get_class (&msg) == STUN_ERROR ? "unauthorized" : "ok");
|
||
|
||
/* unathorized => resend with realm and nonce */
|
||
if (stun_message_get_class (&msg) == STUN_ERROR) {
|
||
int code = -1;
|
||
uint8_t *sent_realm = NULL;
|
||
uint8_t *recv_realm = NULL;
|
||
uint16_t sent_realm_len = 0;
|
||
uint16_t recv_realm_len = 0;
|
||
|
||
sent_realm =
|
||
(uint8_t *) stun_message_find (
|
||
¤t_create_permission_msg->message,
|
||
STUN_ATTRIBUTE_REALM, &sent_realm_len);
|
||
recv_realm =
|
||
(uint8_t *) stun_message_find (&msg,
|
||
STUN_ATTRIBUTE_REALM, &recv_realm_len);
|
||
|
||
/* check for unauthorized error response */
|
||
if (stun_message_find_error (&msg, &code) ==
|
||
STUN_MESSAGE_RETURN_SUCCESS &&
|
||
(code == STUN_ERROR_STALE_NONCE ||
|
||
(code == STUN_ERROR_UNAUTHORIZED &&
|
||
!(recv_realm != NULL &&
|
||
recv_realm_len > 0 &&
|
||
recv_realm_len == sent_realm_len &&
|
||
sent_realm != NULL &&
|
||
memcmp (sent_realm, recv_realm,
|
||
sent_realm_len) == 0)))) {
|
||
|
||
priv->pending_permissions = g_list_delete_link (
|
||
priv->pending_permissions, i);
|
||
g_free (current_create_permission_msg);
|
||
current_create_permission_msg = NULL;
|
||
|
||
nice_udp_turn_socket_cache_realm_nonce_locked (sock, &msg);
|
||
/* resend CreatePermission */
|
||
priv_send_create_permission (priv, &to);
|
||
goto done;
|
||
}
|
||
}
|
||
/* If we get an error, we just assume the server somehow
|
||
doesn't support permissions and we ignore the error and
|
||
fake a successful completion. If the server needs a permission
|
||
but it failed to create it, then the connchecks will fail. */
|
||
priv_remove_sent_permission_for_peer (priv, &to);
|
||
priv_add_permission_for_peer (priv, &to);
|
||
|
||
/* install timer to schedule refresh of the permission */
|
||
/* (will not schedule refresh if we got an error) */
|
||
if (stun_message_get_class (&msg) == STUN_RESPONSE &&
|
||
!priv->permission_timeout_source) {
|
||
priv->permission_timeout_source =
|
||
priv_timeout_add_seconds_with_context (priv,
|
||
STUN_PERMISSION_TIMEOUT, priv_permission_timeout,
|
||
priv);
|
||
}
|
||
|
||
/* send enqued data */
|
||
socket_dequeue_all_data (priv, &to);
|
||
|
||
priv->pending_permissions = g_list_delete_link (
|
||
priv->pending_permissions, i);
|
||
g_free (current_create_permission_msg);
|
||
current_create_permission_msg = NULL;
|
||
|
||
break;
|
||
}
|
||
}
|
||
|
||
goto done;
|
||
} else if (stun_message_get_class (&msg) == STUN_INDICATION &&
|
||
stun_message_get_method (&msg) == STUN_IND_DATA) {
|
||
uint16_t data_len;
|
||
uint8_t *data;
|
||
union {
|
||
struct sockaddr_storage storage;
|
||
struct sockaddr addr;
|
||
} sa;
|
||
socklen_t from_len = sizeof (sa);
|
||
|
||
if (priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_DRAFT9 ||
|
||
priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_RFC5766) {
|
||
if (stun_message_find_xor_addr (&msg, STUN_ATTRIBUTE_REMOTE_ADDRESS,
|
||
&sa.storage, &from_len) !=
|
||
STUN_MESSAGE_RETURN_SUCCESS)
|
||
goto recv;
|
||
} else {
|
||
if (stun_message_find_addr (&msg, STUN_ATTRIBUTE_REMOTE_ADDRESS,
|
||
&sa.storage, &from_len) !=
|
||
STUN_MESSAGE_RETURN_SUCCESS)
|
||
goto recv;
|
||
}
|
||
|
||
data = (uint8_t *) stun_message_find (&msg, STUN_ATTRIBUTE_DATA,
|
||
&data_len);
|
||
|
||
if (data == NULL)
|
||
goto recv;
|
||
|
||
nice_address_set_from_sockaddr (from, &sa.addr);
|
||
|
||
if (priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_RFC5766 &&
|
||
!priv_has_permission_for_peer (priv, from)) {
|
||
if (!priv_has_sent_permission_for_peer (priv, from)) {
|
||
priv_send_create_permission (priv, from);
|
||
}
|
||
}
|
||
|
||
*from_sock = sock;
|
||
memmove (buf, data, len > data_len ? data_len : len);
|
||
g_mutex_unlock (&mutex);
|
||
return len > data_len ? data_len : len;
|
||
} else {
|
||
goto recv;
|
||
}
|
||
}
|
||
}
|
||
|
||
recv:
|
||
for (l = priv->channels; l; l = l->next) {
|
||
ChannelBinding *b = l->data;
|
||
if (priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_DRAFT9 ||
|
||
priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_RFC5766) {
|
||
if (b->channel == ntohs(recv_buf.u16[0])) {
|
||
recv_len = ntohs (recv_buf.u16[1]);
|
||
recv_buf.u8 += sizeof(uint32_t);
|
||
binding = b;
|
||
break;
|
||
}
|
||
} else {
|
||
binding = b;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (binding) {
|
||
*from = binding->peer;
|
||
*from_sock = sock;
|
||
} else {
|
||
*from = *recv_from;
|
||
}
|
||
|
||
memmove (buf, recv_buf.u8, len > recv_len ? recv_len : len);
|
||
g_mutex_unlock (&mutex);
|
||
return len > recv_len ? recv_len : len;
|
||
|
||
msn_google_lock:
|
||
|
||
if (priv->current_binding) {
|
||
GList *i = priv->channels;
|
||
for (; i; i = i->next) {
|
||
ChannelBinding *b = i->data;
|
||
g_free (b);
|
||
}
|
||
g_list_free (priv->channels);
|
||
priv->channels = g_list_append (NULL, priv->current_binding);
|
||
priv->current_binding = NULL;
|
||
priv_process_pending_bindings (priv);
|
||
}
|
||
|
||
done:
|
||
g_mutex_unlock (&mutex);
|
||
return 0;
|
||
}
|
||
|
||
gboolean
|
||
nice_udp_turn_socket_set_peer (NiceSocket *sock, NiceAddress *peer)
|
||
{
|
||
UdpTurnPriv *priv;
|
||
gboolean ret;
|
||
|
||
g_mutex_lock (&mutex);
|
||
|
||
priv = (UdpTurnPriv *) sock->priv;
|
||
|
||
ret = priv_add_channel_binding (priv, peer);
|
||
|
||
g_mutex_unlock (&mutex);
|
||
|
||
return ret;
|
||
}
|
||
|
||
static void
|
||
priv_process_pending_bindings (UdpTurnPriv *priv)
|
||
{
|
||
gboolean ret = FALSE;
|
||
|
||
while (priv->pending_bindings != NULL && ret == FALSE) {
|
||
NiceAddress *peer = priv->pending_bindings->data;
|
||
ret = priv_add_channel_binding (priv, peer);
|
||
priv->pending_bindings = g_list_remove (priv->pending_bindings, peer);
|
||
nice_address_free (peer);
|
||
}
|
||
|
||
/* If no new channel bindings are in progress and there are no
|
||
pending bindings, then renew the soon to be expired bindings */
|
||
if (priv->pending_bindings == NULL && priv->current_binding_msg == NULL) {
|
||
GList *i = NULL;
|
||
|
||
/* find binding to renew */
|
||
for (i = priv->channels ; i; i = i->next) {
|
||
ChannelBinding *b = i->data;
|
||
if (b->renew) {
|
||
priv_send_channel_bind (priv, b->channel, &b->peer);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
static gboolean
|
||
priv_retransmissions_tick_unlocked (UdpTurnPriv *priv)
|
||
{
|
||
gboolean ret = FALSE;
|
||
|
||
if (priv->current_binding_msg) {
|
||
switch (stun_timer_refresh (&priv->current_binding_msg->timer)) {
|
||
case STUN_USAGE_TIMER_RETURN_TIMEOUT:
|
||
{
|
||
/* Time out */
|
||
StunTransactionId id;
|
||
|
||
stun_message_id (&priv->current_binding_msg->message, id);
|
||
stun_agent_forget_transaction (&priv->agent, id);
|
||
|
||
g_free (priv->current_binding);
|
||
priv->current_binding = NULL;
|
||
g_free (priv->current_binding_msg);
|
||
priv->current_binding_msg = NULL;
|
||
|
||
|
||
priv_process_pending_bindings (priv);
|
||
break;
|
||
}
|
||
case STUN_USAGE_TIMER_RETURN_RETRANSMIT:
|
||
/* Retransmit */
|
||
_socket_send_wrapped (priv->base_socket, &priv->server_addr,
|
||
stun_message_length (&priv->current_binding_msg->message),
|
||
(gchar *)priv->current_binding_msg->buffer, FALSE);
|
||
ret = TRUE;
|
||
break;
|
||
case STUN_USAGE_TIMER_RETURN_SUCCESS:
|
||
ret = TRUE;
|
||
break;
|
||
default:
|
||
/* Nothing to do. */
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (ret)
|
||
priv_schedule_tick (priv);
|
||
return ret;
|
||
}
|
||
|
||
static gboolean
|
||
priv_retransmissions_create_permission_tick_unlocked (UdpTurnPriv *priv, GList *list_element)
|
||
{
|
||
gboolean ret = FALSE;
|
||
TURNMessage *current_create_permission_msg;
|
||
|
||
current_create_permission_msg = (TURNMessage *)list_element->data;
|
||
|
||
if (current_create_permission_msg) {
|
||
switch (stun_timer_refresh (¤t_create_permission_msg->timer)) {
|
||
case STUN_USAGE_TIMER_RETURN_TIMEOUT:
|
||
{
|
||
/* Time out */
|
||
StunTransactionId id;
|
||
NiceAddress to;
|
||
union {
|
||
struct sockaddr_storage storage;
|
||
struct sockaddr addr;
|
||
} addr;
|
||
socklen_t addr_len = sizeof(addr);
|
||
|
||
stun_message_id (¤t_create_permission_msg->message, id);
|
||
stun_agent_forget_transaction (&priv->agent, id);
|
||
stun_message_find_xor_addr (
|
||
¤t_create_permission_msg->message,
|
||
STUN_ATTRIBUTE_XOR_PEER_ADDRESS, &addr.storage, &addr_len);
|
||
nice_address_set_from_sockaddr (&to, &addr.addr);
|
||
|
||
priv_remove_sent_permission_for_peer (priv, &to);
|
||
priv->pending_permissions = g_list_delete_link (
|
||
priv->pending_permissions, list_element);
|
||
g_free (current_create_permission_msg);
|
||
current_create_permission_msg = NULL;
|
||
|
||
/* we got a timeout when retransmitting a CreatePermission
|
||
message, assume we can just send the data, the server
|
||
might not support RFC TURN, or connectivity check will
|
||
fail eventually anyway */
|
||
priv_add_permission_for_peer (priv, &to);
|
||
|
||
socket_dequeue_all_data (priv, &to);
|
||
|
||
break;
|
||
}
|
||
case STUN_USAGE_TIMER_RETURN_RETRANSMIT:
|
||
/* Retransmit */
|
||
_socket_send_wrapped (priv->base_socket, &priv->server_addr,
|
||
stun_message_length (¤t_create_permission_msg->message),
|
||
(gchar *)current_create_permission_msg->buffer, FALSE);
|
||
ret = TRUE;
|
||
break;
|
||
case STUN_USAGE_TIMER_RETURN_SUCCESS:
|
||
ret = TRUE;
|
||
break;
|
||
default:
|
||
/* Nothing to do. */
|
||
break;
|
||
}
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
static gboolean
|
||
priv_retransmissions_tick (gpointer pointer)
|
||
{
|
||
UdpTurnPriv *priv = pointer;
|
||
|
||
g_mutex_lock (&mutex);
|
||
if (g_source_is_destroyed (g_main_current_source ())) {
|
||
nice_debug ("Source was destroyed. Avoided race condition in "
|
||
"udp-turn.c:priv_permission_timeout");
|
||
|
||
g_mutex_unlock (&mutex);
|
||
return G_SOURCE_REMOVE;
|
||
}
|
||
|
||
if (priv_retransmissions_tick_unlocked (priv) == FALSE) {
|
||
if (priv->tick_source_channel_bind != NULL) {
|
||
g_source_destroy (priv->tick_source_channel_bind);
|
||
g_source_unref (priv->tick_source_channel_bind);
|
||
priv->tick_source_channel_bind = NULL;
|
||
}
|
||
}
|
||
|
||
g_mutex_unlock (&mutex);
|
||
|
||
return G_SOURCE_REMOVE;
|
||
}
|
||
|
||
static gboolean
|
||
priv_retransmissions_create_permission_tick (gpointer pointer)
|
||
{
|
||
UdpTurnPriv *priv = pointer;
|
||
|
||
g_mutex_lock (&mutex);
|
||
if (g_source_is_destroyed (g_main_current_source ())) {
|
||
nice_debug ("Source was destroyed. Avoided race condition in "
|
||
"udp-turn.c:priv_permission_timeout");
|
||
|
||
g_mutex_unlock (&mutex);
|
||
return G_SOURCE_REMOVE;
|
||
}
|
||
|
||
/* This will call priv_retransmissions_create_permission_tick_unlocked() for
|
||
* every pending permission with an expired timer and will create a new timer
|
||
* if there are pending permissions that require it */
|
||
priv_schedule_tick (priv);
|
||
|
||
g_mutex_unlock (&mutex);
|
||
|
||
return G_SOURCE_REMOVE;
|
||
}
|
||
|
||
static void
|
||
priv_schedule_tick (UdpTurnPriv *priv)
|
||
{
|
||
GList *i, *next, *prev;
|
||
TURNMessage *current_create_permission_msg;
|
||
guint min_timeout = G_MAXUINT;
|
||
|
||
if (priv->tick_source_channel_bind != NULL) {
|
||
g_source_destroy (priv->tick_source_channel_bind);
|
||
g_source_unref (priv->tick_source_channel_bind);
|
||
priv->tick_source_channel_bind = NULL;
|
||
}
|
||
|
||
if (priv->current_binding_msg) {
|
||
guint timeout = stun_timer_remainder (&priv->current_binding_msg->timer);
|
||
if (timeout > 0) {
|
||
priv->tick_source_channel_bind =
|
||
priv_timeout_add_with_context (priv, timeout,
|
||
priv_retransmissions_tick, priv);
|
||
} else {
|
||
priv_retransmissions_tick_unlocked (priv);
|
||
}
|
||
}
|
||
|
||
if (priv->tick_source_create_permission != NULL) {
|
||
g_source_destroy (priv->tick_source_create_permission);
|
||
g_source_unref (priv->tick_source_create_permission);
|
||
priv->tick_source_create_permission = NULL;
|
||
}
|
||
|
||
for (i = priv->pending_permissions, prev = NULL; i; i = next) {
|
||
guint timeout;
|
||
|
||
current_create_permission_msg = (TURNMessage *)i->data;
|
||
next = i->next;
|
||
|
||
timeout = stun_timer_remainder (¤t_create_permission_msg->timer);
|
||
|
||
if (timeout > 0) {
|
||
min_timeout = MIN (min_timeout, timeout);
|
||
prev = i;
|
||
} else {
|
||
/* This could either delete the permission from the list, or it could
|
||
* refresh it, changing its timeout value */
|
||
priv_retransmissions_create_permission_tick_unlocked (priv, i);
|
||
if (prev == NULL)
|
||
next = priv->pending_permissions;
|
||
else
|
||
next = prev->next;
|
||
}
|
||
}
|
||
|
||
/* We create one timer for the minimal timeout we need */
|
||
if (min_timeout != G_MAXUINT) {
|
||
priv->tick_source_create_permission =
|
||
priv_timeout_add_with_context (priv, min_timeout,
|
||
priv_retransmissions_create_permission_tick,
|
||
priv);
|
||
}
|
||
}
|
||
|
||
static void
|
||
priv_send_turn_message (UdpTurnPriv *priv, TURNMessage *msg)
|
||
{
|
||
size_t stun_len = stun_message_length (&msg->message);
|
||
|
||
if (priv->current_binding_msg) {
|
||
g_free (priv->current_binding_msg);
|
||
priv->current_binding_msg = NULL;
|
||
}
|
||
|
||
if (nice_socket_is_reliable (priv->base_socket)) {
|
||
_socket_send_wrapped (priv->base_socket, &priv->server_addr,
|
||
stun_len, (gchar *)msg->buffer, TRUE);
|
||
stun_timer_start_reliable (&msg->timer,
|
||
STUN_TIMER_DEFAULT_RELIABLE_TIMEOUT);
|
||
} else {
|
||
if (_socket_send_wrapped (priv->base_socket, &priv->server_addr,
|
||
stun_len, (gchar *)msg->buffer, TRUE) < 0)
|
||
_socket_send_wrapped (priv->base_socket, &priv->server_addr,
|
||
stun_len, (gchar *)msg->buffer, FALSE);
|
||
stun_timer_start (&msg->timer, STUN_TIMER_DEFAULT_TIMEOUT,
|
||
STUN_TIMER_DEFAULT_MAX_RETRANSMISSIONS);
|
||
}
|
||
|
||
priv->current_binding_msg = msg;
|
||
priv_schedule_tick (priv);
|
||
}
|
||
|
||
static gboolean
|
||
priv_send_create_permission(UdpTurnPriv *priv,
|
||
const NiceAddress *peer)
|
||
{
|
||
guint msg_buf_len;
|
||
gssize res = 0;
|
||
TURNMessage *msg = g_new0 (TURNMessage, 1);
|
||
union {
|
||
struct sockaddr_storage storage;
|
||
struct sockaddr addr;
|
||
} addr;
|
||
|
||
/* register this peer as being pening a permission (if not already pending) */
|
||
if (!priv_has_sent_permission_for_peer (priv, peer)) {
|
||
priv_add_sent_permission_for_peer (priv, peer);
|
||
}
|
||
|
||
nice_address_copy_to_sockaddr (peer, &addr.addr);
|
||
|
||
/* send CreatePermission */
|
||
msg_buf_len = stun_usage_turn_create_permission(&priv->agent, &msg->message,
|
||
msg->buffer,
|
||
sizeof(msg->buffer),
|
||
priv->username,
|
||
priv->username_len,
|
||
priv->password,
|
||
priv->password_len,
|
||
priv->cached_realm, priv->cached_realm_len,
|
||
priv->cached_nonce, priv->cached_nonce_len,
|
||
&addr.storage,
|
||
STUN_USAGE_TURN_COMPATIBILITY_RFC5766);
|
||
|
||
if (msg_buf_len > 0) {
|
||
if (nice_socket_is_reliable (priv->base_socket)) {
|
||
res = _socket_send_wrapped (priv->base_socket, &priv->server_addr,
|
||
msg_buf_len, (gchar *) msg->buffer, TRUE);
|
||
} else {
|
||
res = _socket_send_wrapped (priv->base_socket, &priv->server_addr,
|
||
msg_buf_len, (gchar *) msg->buffer, TRUE);
|
||
if (res < 0)
|
||
res = _socket_send_wrapped (priv->base_socket, &priv->server_addr,
|
||
msg_buf_len, (gchar *) msg->buffer, FALSE);
|
||
}
|
||
|
||
if (res < 0) {
|
||
g_free(msg);
|
||
goto done;
|
||
}
|
||
|
||
if (nice_socket_is_reliable (priv->base_socket)) {
|
||
stun_timer_start_reliable (&msg->timer,
|
||
STUN_TIMER_DEFAULT_RELIABLE_TIMEOUT);
|
||
} else {
|
||
stun_timer_start (&msg->timer, STUN_TIMER_DEFAULT_TIMEOUT,
|
||
STUN_TIMER_DEFAULT_MAX_RETRANSMISSIONS);
|
||
}
|
||
|
||
priv->pending_permissions = g_list_append (priv->pending_permissions, msg);
|
||
priv_schedule_tick (priv);
|
||
} else {
|
||
g_free(msg);
|
||
}
|
||
|
||
done:
|
||
return (res > 0);
|
||
}
|
||
|
||
static gboolean
|
||
priv_send_channel_bind (UdpTurnPriv *priv, uint16_t channel,
|
||
const NiceAddress *peer)
|
||
{
|
||
uint32_t channel_attr = channel << 16;
|
||
size_t stun_len;
|
||
union {
|
||
struct sockaddr_storage storage;
|
||
struct sockaddr addr;
|
||
} sa;
|
||
TURNMessage *msg = g_new0 (TURNMessage, 1);
|
||
|
||
nice_address_copy_to_sockaddr (peer, &sa.addr);
|
||
|
||
if (!stun_agent_init_request (&priv->agent, &msg->message,
|
||
msg->buffer, sizeof(msg->buffer),
|
||
STUN_CHANNELBIND)) {
|
||
g_free (msg);
|
||
return FALSE;
|
||
}
|
||
|
||
if (stun_message_append32 (&msg->message, STUN_ATTRIBUTE_CHANNEL_NUMBER,
|
||
channel_attr) != STUN_MESSAGE_RETURN_SUCCESS) {
|
||
g_free (msg);
|
||
return FALSE;
|
||
}
|
||
|
||
if (stun_message_append_xor_addr (&msg->message, STUN_ATTRIBUTE_PEER_ADDRESS,
|
||
&sa.storage,
|
||
sizeof(sa))
|
||
!= STUN_MESSAGE_RETURN_SUCCESS) {
|
||
g_free (msg);
|
||
return FALSE;
|
||
}
|
||
|
||
if (priv->username != NULL && priv->username_len > 0 &&
|
||
priv->cached_realm != NULL && priv->cached_realm_len > 0 &&
|
||
priv->cached_nonce != NULL && priv->cached_nonce_len > 0) {
|
||
|
||
if (stun_message_append_bytes (&msg->message, STUN_ATTRIBUTE_USERNAME,
|
||
priv->username, priv->username_len)
|
||
!= STUN_MESSAGE_RETURN_SUCCESS) {
|
||
g_free (msg);
|
||
return FALSE;
|
||
}
|
||
|
||
if (stun_message_append_bytes (&msg->message, STUN_ATTRIBUTE_REALM,
|
||
priv->cached_realm, priv->cached_realm_len)
|
||
!= STUN_MESSAGE_RETURN_SUCCESS) {
|
||
g_free (msg);
|
||
return 0;
|
||
}
|
||
|
||
if (stun_message_append_bytes (&msg->message, STUN_ATTRIBUTE_NONCE,
|
||
priv->cached_nonce, priv->cached_nonce_len)
|
||
!= STUN_MESSAGE_RETURN_SUCCESS) {
|
||
g_free (msg);
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
stun_len = stun_agent_finish_message (&priv->agent, &msg->message,
|
||
priv->password, priv->password_len);
|
||
|
||
if (stun_len > 0) {
|
||
priv_send_turn_message (priv, msg);
|
||
return TRUE;
|
||
}
|
||
|
||
g_free (msg);
|
||
return FALSE;
|
||
}
|
||
|
||
static gboolean
|
||
priv_add_channel_binding (UdpTurnPriv *priv, const NiceAddress *peer)
|
||
{
|
||
size_t stun_len;
|
||
union {
|
||
struct sockaddr_storage storage;
|
||
struct sockaddr addr;
|
||
} sa;
|
||
|
||
nice_address_copy_to_sockaddr (peer, &sa.addr);
|
||
|
||
if (priv->current_binding) {
|
||
NiceAddress * pending= nice_address_new ();
|
||
*pending = *peer;
|
||
priv->pending_bindings = g_list_append (priv->pending_bindings, pending);
|
||
return FALSE;
|
||
}
|
||
|
||
if (priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_DRAFT9 ||
|
||
priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_RFC5766) {
|
||
uint16_t channel = 0x4000;
|
||
GList *i = priv->channels;
|
||
for (; i; i = i->next) {
|
||
ChannelBinding *b = i->data;
|
||
if (channel == b->channel) {
|
||
i = priv->channels;
|
||
channel++;
|
||
continue;
|
||
}
|
||
}
|
||
|
||
if (channel >= 0x4000 && channel < 0xffff) {
|
||
gboolean ret = priv_send_channel_bind (priv, channel, peer);
|
||
if (ret) {
|
||
priv->current_binding = g_new0 (ChannelBinding, 1);
|
||
priv->current_binding->channel = channel;
|
||
priv->current_binding->peer = *peer;
|
||
}
|
||
return ret;
|
||
}
|
||
return FALSE;
|
||
} else if (priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_MSN ||
|
||
priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_OC2007) {
|
||
TURNMessage *msg = g_new0 (TURNMessage, 1);
|
||
if (!stun_agent_init_request (&priv->agent, &msg->message,
|
||
msg->buffer, sizeof(msg->buffer),
|
||
STUN_OLD_SET_ACTIVE_DST)) {
|
||
g_free (msg);
|
||
return FALSE;
|
||
}
|
||
|
||
if (stun_message_append32 (&msg->message, STUN_ATTRIBUTE_MAGIC_COOKIE,
|
||
TURN_MAGIC_COOKIE)
|
||
!= STUN_MESSAGE_RETURN_SUCCESS) {
|
||
g_free (msg);
|
||
return FALSE;
|
||
}
|
||
|
||
if (priv->username != NULL && priv->username_len > 0) {
|
||
if (stun_message_append_bytes (&msg->message, STUN_ATTRIBUTE_USERNAME,
|
||
priv->username, priv->username_len)
|
||
!= STUN_MESSAGE_RETURN_SUCCESS) {
|
||
g_free (msg);
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
if (priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_OC2007) {
|
||
if (priv->ms_connection_id_valid)
|
||
stun_message_append_ms_connection_id(&msg->message,
|
||
priv->ms_connection_id, ++priv->ms_sequence_num);
|
||
|
||
stun_message_ensure_ms_realm(&msg->message, priv->ms_realm);
|
||
}
|
||
|
||
if (stun_message_append_addr (&msg->message,
|
||
STUN_ATTRIBUTE_DESTINATION_ADDRESS,
|
||
&sa.addr, sizeof(sa))
|
||
!= STUN_MESSAGE_RETURN_SUCCESS) {
|
||
g_free (msg);
|
||
return FALSE;
|
||
}
|
||
|
||
stun_len = stun_agent_finish_message (&priv->agent, &msg->message,
|
||
priv->password, priv->password_len);
|
||
|
||
if (stun_len > 0) {
|
||
priv->current_binding = g_new0 (ChannelBinding, 1);
|
||
priv->current_binding->channel = 0;
|
||
priv->current_binding->peer = *peer;
|
||
priv_send_turn_message (priv, msg);
|
||
return TRUE;
|
||
}
|
||
g_free (msg);
|
||
return FALSE;
|
||
} else if (priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_GOOGLE) {
|
||
priv->current_binding = g_new0 (ChannelBinding, 1);
|
||
priv->current_binding->channel = 0;
|
||
priv->current_binding->peer = *peer;
|
||
return TRUE;
|
||
} else {
|
||
return FALSE;
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
void
|
||
nice_udp_turn_socket_set_ms_realm(NiceSocket *sock, StunMessage *msg)
|
||
{
|
||
UdpTurnPriv *priv = (UdpTurnPriv *)sock->priv;
|
||
uint16_t alen;
|
||
const uint8_t *realm = stun_message_find(msg, STUN_ATTRIBUTE_REALM, &alen);
|
||
|
||
if (realm && alen <= STUN_MAX_MS_REALM_LEN) {
|
||
g_mutex_lock (&mutex);
|
||
memcpy(priv->ms_realm, realm, alen);
|
||
priv->ms_realm[alen] = '\0';
|
||
g_mutex_unlock (&mutex);
|
||
}
|
||
}
|
||
|
||
void
|
||
nice_udp_turn_socket_set_ms_connection_id (NiceSocket *sock, StunMessage *msg)
|
||
{
|
||
UdpTurnPriv *priv = (UdpTurnPriv *)sock->priv;
|
||
uint16_t alen;
|
||
const uint8_t *ms_seq_num = stun_message_find(msg,
|
||
STUN_ATTRIBUTE_MS_SEQUENCE_NUMBER, &alen);
|
||
|
||
|
||
if (ms_seq_num && alen == 24) {
|
||
g_mutex_lock (&mutex);
|
||
memcpy (priv->ms_connection_id, ms_seq_num, 20);
|
||
priv->ms_sequence_num = ntohl((uint32_t)*(ms_seq_num + 20));
|
||
priv->ms_connection_id_valid = TRUE;
|
||
g_mutex_unlock (&mutex);
|
||
}
|
||
}
|