udp-turn.c 65.7 KB
Newer Older
1 2 3 4 5
/*
 * This file is part of the Nice GLib ICE library.
 *
 * (C) 2008 Collabora Ltd.
 *  Contact: Youness Alaoui
6
 * (C) 2008 Nokia Corporation
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 *
 * 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:
Olivier Crête's avatar
Olivier Crête committed
24
 *   Youness Alaoui, Collabora Ltd.
25 26 27 28 29 30 31 32 33 34 35 36 37
 *
 * 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.
 */

/*
38
 * Implementation of TURN
39 40 41 42 43 44 45 46
 */
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <string.h>
#include <errno.h>
#include <fcntl.h>
47
#include <stdlib.h>
48

49
#include "udp-turn.h"
50 51
#include "stun/stunagent.h"
#include "stun/usages/timer.h"
52
#include "agent-priv.h"
53

54
#define STUN_END_TIMEOUT 8000
55
#define STUN_MAX_MS_REALM_LEN 128 // as defined in [MS-TURN]
56 57 58
#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 */
59

60
typedef struct {
61
  StunMessage message;
62
  uint8_t buffer[STUN_MAX_MESSAGE_SIZE];
63
  StunTimer timer;
64
} TURNMessage;
65

66 67 68
typedef struct {
  NiceAddress peer;
  uint16_t channel;
69
  gboolean renew;
70
  GSource *timeout_source;
71 72
} ChannelBinding;

73
typedef struct {
74
  GMainContext *ctx;
75
  StunAgent agent;
76
  GList *channels;
77
  GList *pending_bindings;
78
  ChannelBinding *current_binding;
79
  TURNMessage *current_binding_msg;
80
  GList *pending_permissions;
81
  GSource *tick_source_channel_bind;
82
  GSource *tick_source_create_permission;
83
  NiceSocket *base_socket;
84
  NiceAddress server_addr;
85
  uint8_t *username;
86
  gsize username_len;
87
  uint8_t *password;
88
  gsize password_len;
89
  NiceTurnSocketCompatibility compatibility;
90
  GQueue *send_requests;
91 92 93 94
  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;
95
  GList *permissions;           /* the peers (NiceAddress) for which
Marcus Lundblad's avatar
Marcus Lundblad committed
96
                                   there is an installed permission */
97
  GList *sent_permissions; /* ongoing permission installed */
98
  GHashTable *send_data_queues; /* stores a send data queue for per peer */
99
  GSource *permission_timeout_source;      /* timer used to invalidate
Marcus Lundblad's avatar
Marcus Lundblad committed
100
                                           permissions */
101 102 103 104 105

  guint8 *cached_realm;
  uint16_t cached_realm_len;
  guint8 *cached_nonce;
  uint16_t cached_nonce_len;
106
} UdpTurnPriv;
107

108

109 110 111
typedef struct {
  StunTransactionId id;
  GSource *source;
112
  UdpTurnPriv *priv;
113
} SendRequest;
114

115
/* used to store data sent while obtaining a permission */
116 117 118
typedef struct {
  gchar *data;
  guint data_len;
119
  gboolean reliable;
120 121
} SendData;

122
static void socket_close (NiceSocket *sock);
123 124
static gint socket_recv_messages (NiceSocket *sock,
    NiceInputMessage *recv_messages, guint n_recv_messages);
125
static gint socket_send_messages (NiceSocket *sock, const NiceAddress *to,
126
    const NiceOutputMessage *messages, guint n_messages);
127 128
static gint socket_send_messages_reliable (NiceSocket *sock,
    const NiceAddress *to, const NiceOutputMessage *messages, guint n_messages);
129
static gboolean socket_is_reliable (NiceSocket *sock);
130 131 132
static gboolean socket_can_send (NiceSocket *sock, NiceAddress *addr);
static void socket_set_writable_callback (NiceSocket *sock,
    NiceSocketWritableCb callback, gpointer user_data);
133
static gboolean socket_is_based_on (NiceSocket *sock, NiceSocket *other);
134

135 136
static void priv_process_pending_bindings (UdpTurnPriv *priv);
static gboolean priv_retransmissions_tick_unlocked (UdpTurnPriv *priv);
137
static gboolean priv_retransmissions_tick (gpointer pointer);
138 139
static void priv_schedule_tick (UdpTurnPriv *priv);
static void priv_send_turn_message (UdpTurnPriv *priv, TURNMessage *msg);
140
static gboolean priv_send_create_permission (UdpTurnPriv *priv,
Youness Alaoui's avatar
Youness Alaoui committed
141
    const NiceAddress *peer);
142
static gboolean priv_send_channel_bind (UdpTurnPriv *priv,
Youness Alaoui's avatar
Youness Alaoui committed
143 144
    uint16_t channel,
    const NiceAddress *peer);
145
static gboolean priv_add_channel_binding (UdpTurnPriv *priv,
Youness Alaoui's avatar
Youness Alaoui committed
146
    const NiceAddress *peer);
147
static gboolean priv_forget_send_request (gpointer pointer);
148
static void priv_clear_permissions (UdpTurnPriv *priv);
149

150 151 152
static guint
priv_nice_address_hash (gconstpointer data)
{
153 154 155 156
  gchar address[NICE_ADDRESS_STRING_LEN];

  nice_address_to_string ((NiceAddress *) data, address);

157
  return g_str_hash(address);
158 159 160
}

static void
Philip Withnall's avatar
Philip Withnall committed
161
priv_send_data_queue_destroy (gpointer user_data)
162
{
Philip Withnall's avatar
Philip Withnall committed
163
  GQueue *send_queue = (GQueue *) user_data;
164
  GList *i;
165

166 167 168 169 170 171 172
  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);
173
}
174 175

NiceSocket *
176
nice_udp_turn_socket_new (GMainContext *ctx, NiceAddress *addr,
Youness Alaoui's avatar
Youness Alaoui committed
177 178 179
    NiceSocket *base_socket, NiceAddress *server_addr,
    gchar *username, gchar *password,
    NiceTurnSocketCompatibility compatibility)
180
{
181
  UdpTurnPriv *priv;
182 183 184 185
  NiceSocket *sock = g_slice_new0 (NiceSocket);

  if (!sock) {
    return NULL;
186
  }
187

188
  priv = g_new0 (UdpTurnPriv, 1);
189

190 191
  if (compatibility == NICE_TURN_SOCKET_COMPATIBILITY_DRAFT9 ||
      compatibility == NICE_TURN_SOCKET_COMPATIBILITY_RFC5766) {
192
    stun_agent_init (&priv->agent, STUN_ALL_KNOWN_ATTRIBUTES,
Youness Alaoui's avatar
Youness Alaoui committed
193 194
        STUN_COMPATIBILITY_RFC5389,
        STUN_AGENT_USAGE_LONG_TERM_CREDENTIALS);
195 196
  } else if (compatibility == NICE_TURN_SOCKET_COMPATIBILITY_MSN) {
    stun_agent_init (&priv->agent, STUN_ALL_KNOWN_ATTRIBUTES,
Youness Alaoui's avatar
Youness Alaoui committed
197 198 199
        STUN_COMPATIBILITY_RFC3489,
        STUN_AGENT_USAGE_SHORT_TERM_CREDENTIALS |
        STUN_AGENT_USAGE_NO_INDICATION_AUTH);
200 201
  } else if (compatibility == NICE_TURN_SOCKET_COMPATIBILITY_GOOGLE) {
    stun_agent_init (&priv->agent, STUN_ALL_KNOWN_ATTRIBUTES,
Youness Alaoui's avatar
Youness Alaoui committed
202 203 204
        STUN_COMPATIBILITY_RFC3489,
        STUN_AGENT_USAGE_SHORT_TERM_CREDENTIALS |
        STUN_AGENT_USAGE_IGNORE_CREDENTIALS);
205
  } else if (compatibility == NICE_TURN_SOCKET_COMPATIBILITY_OC2007) {
Youness Alaoui's avatar
Youness Alaoui committed
206
    stun_agent_init (&priv->agent, STUN_ALL_KNOWN_ATTRIBUTES,
207 208 209
        STUN_COMPATIBILITY_OC2007,
        STUN_AGENT_USAGE_LONG_TERM_CREDENTIALS |
        STUN_AGENT_USAGE_NO_ALIGNED_ATTRIBUTES);
210 211 212 213
  } else if (compatibility == NICE_TURN_SOCKET_COMPATIBILITY_RFC5766_SHORT_TERM) {
    stun_agent_init (&priv->agent, STUN_ALL_KNOWN_ATTRIBUTES,
        STUN_COMPATIBILITY_RFC5389,
        STUN_AGENT_USAGE_SHORT_TERM_CREDENTIALS | STUN_AGENT_USAGE_IGNORE_CREDENTIALS);
214 215
  }

216 217 218
  priv->channels = NULL;
  priv->current_binding = NULL;
  priv->base_socket = base_socket;
219 220
  if (ctx)
    priv->ctx = g_main_context_ref (ctx);
221

222 223
  if (compatibility == NICE_TURN_SOCKET_COMPATIBILITY_MSN ||
      compatibility == NICE_TURN_SOCKET_COMPATIBILITY_OC2007) {
224 225 226 227
    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);
228
    priv->username_len = (gsize) strlen (username);
229 230 231 232 233
    if (compatibility == NICE_TURN_SOCKET_COMPATIBILITY_GOOGLE) {
      priv->password = NULL;
      priv->password_len = 0;
    } else {
      priv->password = (uint8_t *)g_strdup (password);
234
      priv->password_len = (gsize) strlen (password);
235 236 237 238
    }
  }
  priv->server_addr = *server_addr;
  priv->compatibility = compatibility;
239
  priv->send_requests = g_queue_new ();
240

241
  priv->send_data_queues =
Youness Alaoui's avatar
Youness Alaoui committed
242 243 244 245
      g_hash_table_new_full (priv_nice_address_hash,
          (GEqualFunc) nice_address_equal,
          (GDestroyNotify) nice_address_free,
          priv_send_data_queue_destroy);
246

247
  sock->type = NICE_SOCKET_TYPE_UDP_TURN;
248
  sock->fileno = NULL;
249
  sock->addr = *addr;
250
  sock->send_messages = socket_send_messages;
251
  sock->send_messages_reliable = socket_send_messages_reliable;
252
  sock->recv_messages = socket_recv_messages;
253
  sock->is_reliable = socket_is_reliable;
254 255
  sock->can_send = socket_can_send;
  sock->set_writable_callback = socket_set_writable_callback;
256
  sock->is_based_on = socket_is_based_on;
257 258
  sock->close = socket_close;
  sock->priv = (void *) priv;
259

260
  return sock;
261 262 263
}


264

265 266
static void
socket_close (NiceSocket *sock)
267
{
268
  UdpTurnPriv *priv = (UdpTurnPriv *) sock->priv;
269
  GList *i = NULL;
270

271 272
  for (i = priv->channels; i; i = i->next) {
    ChannelBinding *b = i->data;
273 274 275 276
    if (b->timeout_source) {
      g_source_destroy (b->timeout_source);
      g_source_unref (b->timeout_source);
    }
277 278 279
    g_free (b);
  }
  g_list_free (priv->channels);
280

281
  g_list_foreach (priv->pending_bindings, (GFunc) nice_address_free,
Youness Alaoui's avatar
Youness Alaoui committed
282
      NULL);
283
  g_list_free (priv->pending_bindings);
284

285 286 287 288
  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;
289 290
  }

291 292 293 294 295 296 297
  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;
  }


298
  for (i = g_queue_peek_head_link (priv->send_requests); i; i = i->next) {
299 300 301
    SendRequest *r = i->data;
    g_source_destroy (r->source);
    g_source_unref (r->source);
302 303 304 305
    r->source = NULL;

    stun_agent_forget_transaction (&priv->agent, r->id);

306
    g_slice_free (SendRequest, r);
307

308
  }
309
  g_queue_free (priv->send_requests);
310

311
  priv_clear_permissions (priv);
312 313
  g_list_foreach (priv->sent_permissions, (GFunc) nice_address_free, NULL);
  g_list_free (priv->sent_permissions);
314
  g_hash_table_destroy (priv->send_data_queues);
315

316 317 318 319 320
  if (priv->permission_timeout_source) {
    g_source_destroy (priv->permission_timeout_source);
    g_source_unref (priv->permission_timeout_source);
    priv->permission_timeout_source = NULL;
  }
321

322 323 324
  if (priv->ctx)
    g_main_context_unref (priv->ctx);

325 326
  g_free (priv->current_binding);
  g_free (priv->current_binding_msg);
327 328
  g_list_foreach (priv->pending_permissions, (GFunc) g_free, NULL);
  g_list_free(priv->pending_permissions);
329 330
  g_free (priv->username);
  g_free (priv->password);
331 332
  g_free (priv->cached_realm);
  g_free (priv->cached_nonce);
333
  g_free (priv);
334 335

  sock->priv = NULL;
336 337
}

338
static gint
339 340
socket_recv_messages (NiceSocket *sock,
    NiceInputMessage *recv_messages, guint n_recv_messages)
341
{
342
  UdpTurnPriv *priv = (UdpTurnPriv *) sock->priv;
343 344 345 346
  gint n_messages;
  guint i;
  gboolean error = FALSE;
  guint n_valid_messages;
347

348 349
  /* Make sure socket has not been freed: */
  g_assert (sock->priv != NULL);
350

351
  nice_debug_verbose ("received message on TURN socket");
352

353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
  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 += n_valid_messages) {
    NiceInputMessage *message = &recv_messages[i];
    NiceSocket *dummy;
    NiceAddress from;
    guint8 *buffer;
    gsize buffer_length;
    gint parsed_buffer_length;
    gboolean allocated_buffer = FALSE;

    n_valid_messages = 1;

377 378 379
    if (message->length == 0)
      continue;

380 381 382 383 384 385 386
    /* 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;
387
      buffer_length = message->length;
388
    } else {
389
      nice_debug_verbose ("%s: **WARNING: SLOW PATH**", G_STRFUNC);
390 391 392 393 394 395

      buffer = compact_input_message (message, &buffer_length);
      allocated_buffer = TRUE;
    }

    /* Parse in-place. */
396
    parsed_buffer_length = nice_udp_turn_socket_parse_recv (sock, &dummy,
397 398
        &from, buffer_length, buffer,
        message->from, buffer, buffer_length);
399 400 401 402 403 404 405 406
    message->length = MAX (parsed_buffer_length, 0);

    if (parsed_buffer_length < 0) {
      error = TRUE;
    } else if (parsed_buffer_length == 0) {
      /* A TURN control message which needs ignoring. Re-use this
       * NiceInputMessage in the next loop iteration. */
      n_valid_messages = 0;
407 408
    } else {
      *message->from = from;
409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
    }

    /* Split up the monolithic buffer again into the caller-provided buffers. */
    if (parsed_buffer_length > 0 && allocated_buffer) {
      parsed_buffer_length =
          memcpy_buffer_to_input_message (message, buffer,
              parsed_buffer_length);
    }

    if (allocated_buffer)
      g_free (buffer);

    if (error)
      break;
  }

  /* Was there an error processing the first message? */
  if (error && i == 0)
    return -1;
428

429
  return i;
430
}
431

432
static GSource *
433
priv_timeout_add_with_context (UdpTurnPriv *priv, guint interval,
434
    gboolean seconds, GSourceFunc function, gpointer data)
435 436 437 438 439
{
  GSource *source;

  g_return_val_if_fail (function != NULL, NULL);

440 441 442 443
  if (seconds)
    source = g_timeout_source_new_seconds (interval);
  else
    source = g_timeout_source_new (interval);
444 445 446 447 448 449 450

  g_source_set_callback (source, function, data, NULL);
  g_source_attach (source, priv->ctx);

  return source;
}

451 452 453 454 455 456 457 458 459
static StunMessageReturn
stun_message_append_ms_connection_id(StunMessage *msg,
    uint8_t *ms_connection_id, uint32_t ms_sequence_num)
{
  uint8_t buf[24];

  memcpy(buf, ms_connection_id, 20);
  *(uint32_t*)(buf + 20) = htonl(ms_sequence_num);
  return stun_message_append_bytes (msg, STUN_ATTRIBUTE_MS_SEQUENCE_NUMBER,
Youness Alaoui's avatar
Youness Alaoui committed
460
      buf, 24);
461 462 463 464 465 466 467 468 469 470 471 472 473
}

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,
Youness Alaoui's avatar
Youness Alaoui committed
474
        strlen((char *)realm));
475 476 477
  }
}

478
static gboolean
479
priv_is_peer_in_list (const GList *list, const NiceAddress *peer)
480
{
481
  const GList *iter;
482

483
  for (iter = list ; iter ; iter = g_list_next (iter)) {
484 485
    NiceAddress *address = (NiceAddress *) iter->data;

486
    if (nice_address_equal (address, peer))
487 488 489 490
      return TRUE;
  }

  return FALSE;
491 492 493
}

static gboolean
494
priv_has_permission_for_peer (UdpTurnPriv *priv, const NiceAddress *peer)
495
{
496 497
  return priv_is_peer_in_list (priv->permissions, peer);
}
498

499
static gboolean
500
priv_has_sent_permission_for_peer (UdpTurnPriv *priv, const NiceAddress *peer)
501 502 503
{
  return priv_is_peer_in_list (priv->sent_permissions, peer);
}
504

505
static void
506
priv_add_permission_for_peer (UdpTurnPriv *priv, const NiceAddress *peer)
507
{
Youness Alaoui's avatar
Youness Alaoui committed
508 509
  priv->permissions =
      g_list_append (priv->permissions, nice_address_dup (peer));
510 511 512
}

static void
513
priv_add_sent_permission_for_peer (UdpTurnPriv *priv, const NiceAddress *peer)
514 515
{
  priv->sent_permissions =
Youness Alaoui's avatar
Youness Alaoui committed
516
      g_list_append (priv->sent_permissions, nice_address_dup (peer));
517 518 519 520 521 522
}

static GList *
priv_remove_peer_from_list (GList *list, const NiceAddress *peer)
{
  GList *iter;
523

524 525
  for (iter = list ; iter ; iter = g_list_next (iter)) {
    NiceAddress *address = (NiceAddress *) iter->data;
526

527
    if (nice_address_equal (address, peer)) {
528 529
      GList *prev = iter->prev;

530
      nice_address_free (address);
531
      list = g_list_delete_link (list, iter);
532 533 534
      iter = prev;
      if (iter)
        iter = list;
535
    }
536 537
  }

538
  return list;
539 540
}

541
static void
542
priv_remove_sent_permission_for_peer (UdpTurnPriv *priv, const NiceAddress *peer)
543 544
{
  priv->sent_permissions =
Youness Alaoui's avatar
Youness Alaoui committed
545
      priv_remove_peer_from_list (priv->sent_permissions, peer);
546 547 548
}

static void
549
priv_clear_permissions (UdpTurnPriv *priv)
550 551 552 553 554
{
  g_list_foreach (priv->permissions, (GFunc) nice_address_free, NULL);
  g_list_free (priv->permissions);
  priv->permissions = NULL;
}
555

556 557 558 559
static gint
_socket_send_messages_wrapped (NiceSocket *sock, const NiceAddress *to,
    const NiceOutputMessage *messages, guint n_messages, gboolean reliable)
{
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589
  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;
    }

590
    local_bufs = g_alloca ((n_bufs + 1) * sizeof (GOutputVector));
591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614
    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;
  }
615 616 617 618 619 620 621 622
}

static gssize
_socket_send_wrapped (NiceSocket *sock, const NiceAddress *to,
    guint len, const gchar *buf, gboolean reliable)
{
  gint ret;

623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644
  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;
  }
645 646
}

647
static void
648
socket_enqueue_data(UdpTurnPriv *priv, const NiceAddress *to,
649
    guint len, const gchar *buf, gboolean reliable)
650
{
651 652
  SendData *data = g_slice_new0 (SendData);
  GQueue *queue = g_hash_table_lookup (priv->send_data_queues, to);
653

654 655 656
  if (queue == NULL) {
    queue = g_queue_new ();
    g_hash_table_insert (priv->send_data_queues, nice_address_dup (to),
Youness Alaoui's avatar
Youness Alaoui committed
657
        queue);
658 659 660 661
  }

  data->data = g_memdup(buf, len);
  data->data_len = len;
662
  data->reliable = reliable;
663
  g_queue_push_tail (queue, data);
664 665 666
}

static void
667
socket_dequeue_all_data (UdpTurnPriv *priv, const NiceAddress *to)
668
{
669
  GQueue *send_queue = g_hash_table_lookup (priv->send_data_queues, to);
670

671 672 673
  if (send_queue) {
    while (!g_queue_is_empty (send_queue)) {
      SendData *data =
Youness Alaoui's avatar
Youness Alaoui committed
674
          (SendData *) g_queue_pop_head(send_queue);
675

676
      nice_debug_verbose ("dequeuing data");
677 678
      _socket_send_wrapped (priv->base_socket, &priv->server_addr,
          data->data_len, data->data, data->reliable);
679 680 681 682 683 684 685 686

      g_free (data->data);
      g_slice_free (SendData, data);
    }

    /* remove queue from table */
    g_hash_table_remove (priv->send_data_queues, to);
  }
687 688 689
}


690
static gssize
691
socket_send_message (NiceSocket *sock, const NiceAddress *to,
692
    const NiceOutputMessage *message, gboolean reliable)
693
{
694
  UdpTurnPriv *priv = (UdpTurnPriv *) sock->priv;
695 696 697
  StunMessage msg;
  uint8_t buffer[STUN_MAX_MESSAGE_SIZE];
  size_t msg_len;
698 699 700 701
  union {
    struct sockaddr_storage storage;
    struct sockaddr addr;
  } sa;
702
  GList *i;
703
  ChannelBinding *binding = NULL;
704
  gint ret;
705

706 707
  /* Make sure socket has not been freed: */
  g_assert (sock->priv != NULL);
708

709
  for (i = priv->channels; i; i = i->next) {
710
    ChannelBinding *b = i->data;
711
    if (nice_address_equal (&b->peer, to)) {
712 713 714
      binding = b;
      break;
    }
715
  }
716

717
  nice_address_copy_to_sockaddr (to, &sa.addr);
718

719
  if (binding) {
720
    if (priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_DRAFT9 ||
721
        priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_RFC5766_SHORT_TERM ||
722
        priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_RFC5766) {
723 724 725
      gsize message_len = output_message_get_size (message);

      if (message_len + sizeof(uint32_t) <= sizeof(buffer)) {
726 727 728 729
        guint j;
        uint16_t len16, channel16;
        gsize message_offset = 0;

730
        len16 = htons ((uint16_t) message_len);
731 732
        channel16 = htons (binding->channel);

Youness Alaoui's avatar
Youness Alaoui committed
733
        memcpy (buffer, &channel16, sizeof(uint16_t));
734
        memcpy (buffer + sizeof(uint16_t), &len16, sizeof(uint16_t));
735 736 737 738 739 740 741 742 743 744 745

        /* 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;

746
          out_len = MIN (message_len - message_offset, out_buf->size);
747 748 749 750 751
          memcpy (buffer + sizeof (uint32_t) + message_offset,
              out_buf->buffer, out_len);
          message_offset += out_len;
        }

752
        msg_len = message_len + sizeof(uint32_t);
Youness Alaoui's avatar
Youness Alaoui committed
753
      } else {
754
        goto error;
Youness Alaoui's avatar
Youness Alaoui committed
755
      }
756
    } else {
757 758
      ret = _socket_send_messages_wrapped (priv->base_socket,
          &priv->server_addr, message, 1, reliable);
759

760
      if (ret == 1)
761
        return output_message_get_size (message);
762
      return ret;
763
    }
764
  } else {
765 766 767
    guint8 *compacted_buf;
    gsize compacted_buf_len;

768
    if (priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_DRAFT9 ||
769
        priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_RFC5766_SHORT_TERM ||
770
        priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_RFC5766) {
771
      if (!stun_agent_init_indication (&priv->agent, &msg,
Youness Alaoui's avatar
Youness Alaoui committed
772
              buffer, sizeof(buffer), STUN_IND_SEND))
773
        goto error;
774
      if (stun_message_append_xor_addr (&msg, STUN_ATTRIBUTE_PEER_ADDRESS,
775
              &sa.storage, sizeof(sa)) !=
776
          STUN_MESSAGE_RETURN_SUCCESS)
777
        goto error;
778 779 780 781 782 783 784
      if (priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_RFC5766_SHORT_TERM &&
          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;
      }
785 786
    } else {
      if (!stun_agent_init_request (&priv->agent, &msg,
Youness Alaoui's avatar
Youness Alaoui committed
787
              buffer, sizeof(buffer), STUN_SEND))
788
        goto error;
789

790
      if (stun_message_append32 (&msg, STUN_ATTRIBUTE_MAGIC_COOKIE,
791
              TURN_MAGIC_COOKIE) != STUN_MESSAGE_RETURN_SUCCESS)
792
        goto error;
793 794
      if (priv->username != NULL && priv->username_len > 0) {
        if (stun_message_append_bytes (&msg, STUN_ATTRIBUTE_USERNAME,
Youness Alaoui's avatar
Youness Alaoui committed
795
                priv->username, priv->username_len) !=
796
            STUN_MESSAGE_RETURN_SUCCESS)
797
          goto error;
798
      }
799
      if (stun_message_append_addr (&msg, STUN_ATTRIBUTE_DESTINATION_ADDRESS,
800
              &sa.addr, sizeof(sa)) !=
801
          STUN_MESSAGE_RETURN_SUCCESS)
802
        goto error;
803 804 805

      if (priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_GOOGLE &&
          priv->current_binding &&
806
          nice_address_equal (&priv->current_binding->peer, to)) {
807
        stun_message_append32 (&msg, STUN_ATTRIBUTE_OPTIONS, 1);
808
      }
809 810
    }

811 812 813 814 815 816 817 818 819 820
    if (priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_OC2007) {
      stun_message_append32(&msg, STUN_ATTRIBUTE_MS_VERSION, 1);

      if (priv->ms_connection_id_valid)
        stun_message_append_ms_connection_id(&msg, priv->ms_connection_id,
            ++priv->ms_sequence_num);

      stun_message_ensure_ms_realm(&msg, priv->ms_realm);
    }

821 822 823 824 825
    /* 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);

826
    if (stun_message_append_bytes (&msg, STUN_ATTRIBUTE_DATA,
827 828
            compacted_buf, compacted_buf_len) != STUN_MESSAGE_RETURN_SUCCESS) {
      g_free (compacted_buf);
829
      goto error;
830 831 832
    }

    g_free (compacted_buf);
833

834
    /* Finish the message. */