turn.c 50.9 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 "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
  guint 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 86 87 88
  uint8_t *username;
  size_t username_len;
  uint8_t *password;
  size_t 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
  guint permission_timeout_source;      /* timer used to invalidate
Marcus Lundblad's avatar
Marcus Lundblad committed
100
                                           permissions */
101
} TurnPriv;
102

103

104 105 106 107 108
typedef struct {
  StunTransactionId id;
  GSource *source;
  TurnPriv *priv;
} SendRequest;
109

110
/* used to store data sent while obtaining a permission */
111 112 113 114 115
typedef struct {
  gchar *data;
  guint data_len;
} SendData;

116 117
static void socket_close (NiceSocket *sock);
static gint socket_recv (NiceSocket *sock, NiceAddress *from,
Youness Alaoui's avatar
Youness Alaoui committed
118
    guint len, gchar *buf);
119
static gboolean socket_send (NiceSocket *sock, const NiceAddress *to,
Youness Alaoui's avatar
Youness Alaoui committed
120
    guint len, const gchar *buf);
121
static gboolean socket_is_reliable (NiceSocket *sock);
122

123 124 125 126 127
static void priv_process_pending_bindings (TurnPriv *priv);
static gboolean priv_retransmissions_tick_unlocked (TurnPriv *priv);
static gboolean priv_retransmissions_tick (gpointer pointer);
static void priv_schedule_tick (TurnPriv *priv);
static void priv_send_turn_message (TurnPriv *priv, TURNMessage *msg);
128
static gboolean priv_send_create_permission (TurnPriv *priv,  StunMessage *resp,
Youness Alaoui's avatar
Youness Alaoui committed
129
    const NiceAddress *peer);
130
static gboolean priv_send_channel_bind (TurnPriv *priv, StunMessage *resp,
Youness Alaoui's avatar
Youness Alaoui committed
131 132
    uint16_t channel,
    const NiceAddress *peer);
133
static gboolean priv_add_channel_binding (TurnPriv *priv,
Youness Alaoui's avatar
Youness Alaoui committed
134
    const NiceAddress *peer);
135
static gboolean priv_forget_send_request (gpointer pointer);
136
static void priv_clear_permissions (TurnPriv *priv);
137

138 139 140
static guint
priv_nice_address_hash (gconstpointer data)
{
141 142 143 144
  gchar address[NICE_ADDRESS_STRING_LEN];

  nice_address_to_string ((NiceAddress *) data, address);

145
  return g_str_hash(address);
146 147 148
}

static void
Philip Withnall's avatar
Philip Withnall committed
149
priv_send_data_queue_destroy (gpointer user_data)
150
{
Philip Withnall's avatar
Philip Withnall committed
151
  GQueue *send_queue = (GQueue *) user_data;
152
  GList *i;
153

154 155 156 157 158 159 160
  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);
161
}
162 163

NiceSocket *
164
nice_turn_socket_new (GMainContext *ctx, NiceAddress *addr,
Youness Alaoui's avatar
Youness Alaoui committed
165 166 167
    NiceSocket *base_socket, NiceAddress *server_addr,
    gchar *username, gchar *password,
    NiceTurnSocketCompatibility compatibility)
168
{
169
  TurnPriv *priv;
170 171 172 173
  NiceSocket *sock = g_slice_new0 (NiceSocket);

  if (!sock) {
    return NULL;
174
  }
175

176 177
  priv = g_new0 (TurnPriv, 1);

178 179
  if (compatibility == NICE_TURN_SOCKET_COMPATIBILITY_DRAFT9 ||
      compatibility == NICE_TURN_SOCKET_COMPATIBILITY_RFC5766) {
180
    stun_agent_init (&priv->agent, STUN_ALL_KNOWN_ATTRIBUTES,
Youness Alaoui's avatar
Youness Alaoui committed
181 182
        STUN_COMPATIBILITY_RFC5389,
        STUN_AGENT_USAGE_LONG_TERM_CREDENTIALS);
183 184
  } else if (compatibility == NICE_TURN_SOCKET_COMPATIBILITY_MSN) {
    stun_agent_init (&priv->agent, STUN_ALL_KNOWN_ATTRIBUTES,
Youness Alaoui's avatar
Youness Alaoui committed
185 186 187
        STUN_COMPATIBILITY_RFC3489,
        STUN_AGENT_USAGE_SHORT_TERM_CREDENTIALS |
        STUN_AGENT_USAGE_NO_INDICATION_AUTH);
188 189
  } else if (compatibility == NICE_TURN_SOCKET_COMPATIBILITY_GOOGLE) {
    stun_agent_init (&priv->agent, STUN_ALL_KNOWN_ATTRIBUTES,
Youness Alaoui's avatar
Youness Alaoui committed
190 191 192
        STUN_COMPATIBILITY_RFC3489,
        STUN_AGENT_USAGE_SHORT_TERM_CREDENTIALS |
        STUN_AGENT_USAGE_IGNORE_CREDENTIALS);
193
  } else if (compatibility == NICE_TURN_SOCKET_COMPATIBILITY_OC2007) {
Youness Alaoui's avatar
Youness Alaoui committed
194
    stun_agent_init (&priv->agent, STUN_ALL_KNOWN_ATTRIBUTES,
195 196 197 198
        STUN_COMPATIBILITY_OC2007,
        STUN_AGENT_USAGE_NO_INDICATION_AUTH |
        STUN_AGENT_USAGE_LONG_TERM_CREDENTIALS |
        STUN_AGENT_USAGE_NO_ALIGNED_ATTRIBUTES);
199 200
  }

201 202 203
  priv->channels = NULL;
  priv->current_binding = NULL;
  priv->base_socket = base_socket;
204 205
  if (ctx)
    priv->ctx = g_main_context_ref (ctx);
206

207 208
  if (compatibility == NICE_TURN_SOCKET_COMPATIBILITY_MSN ||
      compatibility == NICE_TURN_SOCKET_COMPATIBILITY_OC2007) {
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
    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 = (size_t) 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 = (size_t) strlen (password);
    }
  }
  priv->server_addr = *server_addr;
  priv->compatibility = compatibility;
224
  priv->send_requests = g_queue_new ();
225

226
  priv->send_data_queues =
Youness Alaoui's avatar
Youness Alaoui committed
227 228 229 230
      g_hash_table_new_full (priv_nice_address_hash,
          (GEqualFunc) nice_address_equal,
          (GDestroyNotify) nice_address_free,
          priv_send_data_queue_destroy);
231 232 233 234 235 236 237 238
  sock->addr = *addr;
  sock->fileno = base_socket->fileno;
  sock->send = socket_send;
  sock->recv = socket_recv;
  sock->is_reliable = socket_is_reliable;
  sock->close = socket_close;
  sock->priv = (void *) priv;
  return sock;
239 240 241
}


242

243 244
static void
socket_close (NiceSocket *sock)
245
{
246 247
  TurnPriv *priv = (TurnPriv *) sock->priv;
  GList *i = NULL;
248

249 250
  for (i = priv->channels; i; i = i->next) {
    ChannelBinding *b = i->data;
251 252
    if (b->timeout_source)
      g_source_remove (b->timeout_source);
253 254 255
    g_free (b);
  }
  g_list_free (priv->channels);
256

257
  g_list_foreach (priv->pending_bindings, (GFunc) nice_address_free,
Youness Alaoui's avatar
Youness Alaoui committed
258
      NULL);
259
  g_list_free (priv->pending_bindings);
260

261 262 263 264
  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;
265 266
  }

267 268 269 270 271 272 273
  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;
  }


274
  for (i = g_queue_peek_head_link (priv->send_requests); i; i = i->next) {
275 276 277
    SendRequest *r = i->data;
    g_source_destroy (r->source);
    g_source_unref (r->source);
278 279 280 281
    r->source = NULL;

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

282
    g_slice_free (SendRequest, r);
283

284
  }
285
  g_queue_free (priv->send_requests);
286

287
  priv_clear_permissions (priv);
288 289
  g_list_foreach (priv->sent_permissions, (GFunc) nice_address_free, NULL);
  g_list_free (priv->sent_permissions);
290
  g_hash_table_destroy (priv->send_data_queues);
291 292 293

  if (priv->permission_timeout_source)
    g_source_remove (priv->permission_timeout_source);
294

295 296 297
  if (priv->ctx)
    g_main_context_unref (priv->ctx);

298 299
  g_free (priv->current_binding);
  g_free (priv->current_binding_msg);
300 301
  g_list_foreach (priv->pending_permissions, (GFunc) g_free, NULL);
  g_list_free(priv->pending_permissions);
302 303 304
  g_free (priv->username);
  g_free (priv->password);
  g_free (priv);
305 306
}

307 308
static gint
socket_recv (NiceSocket *sock, NiceAddress *from, guint len, gchar *buf)
309
{
310 311 312 313
  TurnPriv *priv = (TurnPriv *) sock->priv;
  uint8_t recv_buf[STUN_MAX_MESSAGE_SIZE];
  gint recv_len;
  NiceAddress recv_from;
314
  NiceSocket *dummy;
315

316
  nice_debug ("received message on TURN socket");
317

318
  recv_len = nice_socket_recv (priv->base_socket, &recv_from,
Youness Alaoui's avatar
Youness Alaoui committed
319
      sizeof(recv_buf), (gchar *) recv_buf);
320

321 322
  if (recv_len > 0)
    return nice_turn_socket_parse_recv (sock, &dummy, from, len, buf,
Youness Alaoui's avatar
Youness Alaoui committed
323 324
        &recv_from, (gchar *) recv_buf,
        (guint) recv_len);
325 326
  else
    return recv_len;
327
}
328

329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
static GSource *
priv_timeout_add_with_context (TurnPriv *priv, guint interval,
    GSourceFunc function, gpointer data)
{
  GSource *source;

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

345 346 347 348 349 350 351 352 353
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
354
      buf, 24);
355 356 357 358 359 360 361 362 363 364 365 366 367
}

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
368
        strlen((char *)realm));
369 370 371
  }
}

372
static gboolean
373
priv_is_peer_in_list (const GList *list, const NiceAddress *peer)
374
{
375
  const GList *iter;
376

377
  for (iter = list ; iter ; iter = g_list_next (iter)) {
378 379
    NiceAddress *address = (NiceAddress *) iter->data;

380
    if (nice_address_equal (address, peer))
381 382 383 384
      return TRUE;
  }

  return FALSE;
385 386 387
}

static gboolean
388
priv_has_permission_for_peer (TurnPriv *priv, const NiceAddress *peer)
389
{
390 391
  return priv_is_peer_in_list (priv->permissions, peer);
}
392

393 394 395 396 397
static gboolean
priv_has_sent_permission_for_peer (TurnPriv *priv, const NiceAddress *peer)
{
  return priv_is_peer_in_list (priv->sent_permissions, peer);
}
398

399 400 401
static void
priv_add_permission_for_peer (TurnPriv *priv, const NiceAddress *peer)
{
402 403
  priv->permissions =
      g_list_append (priv->permissions, nice_address_dup (peer));
404 405 406 407 408 409
}

static void
priv_add_sent_permission_for_peer (TurnPriv *priv, const NiceAddress *peer)
{
  priv->sent_permissions =
Youness Alaoui's avatar
Youness Alaoui committed
410
      g_list_append (priv->sent_permissions, nice_address_dup (peer));
411 412 413 414 415 416
}

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

418 419
  for (iter = list ; iter ; iter = g_list_next (iter)) {
    NiceAddress *address = (NiceAddress *) iter->data;
420

421
    if (nice_address_equal (address, peer)) {
422
      nice_address_free (address);
423 424
      list = g_list_delete_link (list, iter);
    }
425 426
  }

427
  return list;
428 429
}

430 431 432 433
static void
priv_remove_sent_permission_for_peer (TurnPriv *priv, const NiceAddress *peer)
{
  priv->sent_permissions =
Youness Alaoui's avatar
Youness Alaoui committed
434
      priv_remove_peer_from_list (priv->sent_permissions, peer);
435 436 437 438 439 440 441 442 443
}

static void
priv_clear_permissions (TurnPriv *priv)
{
  g_list_foreach (priv->permissions, (GFunc) nice_address_free, NULL);
  g_list_free (priv->permissions);
  priv->permissions = NULL;
}
444

445
static void
446
socket_enqueue_data(TurnPriv *priv, const NiceAddress *to,
Youness Alaoui's avatar
Youness Alaoui committed
447
    guint len, const gchar *buf)
448
{
449 450
  SendData *data = g_slice_new0 (SendData);
  GQueue *queue = g_hash_table_lookup (priv->send_data_queues, to);
451

452 453 454
  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
455
        queue);
456 457 458 459 460
  }

  data->data = g_memdup(buf, len);
  data->data_len = len;
  g_queue_push_tail (queue, data);
461 462 463
}

static void
464
socket_dequeue_all_data (TurnPriv *priv, const NiceAddress *to)
465
{
466
  GQueue *send_queue = g_hash_table_lookup (priv->send_data_queues, to);
467

468 469 470
  if (send_queue) {
    while (!g_queue_is_empty (send_queue)) {
      SendData *data =
Youness Alaoui's avatar
Youness Alaoui committed
471
          (SendData *) g_queue_pop_head(send_queue);
472

473
      nice_debug ("dequeuing data");
474 475 476 477 478 479 480 481 482
      nice_socket_send (priv->base_socket, to, data->data_len, data->data);

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

    /* remove queue from table */
    g_hash_table_remove (priv->send_data_queues, to);
  }
483 484 485
}


486
static gboolean
487
socket_send (NiceSocket *sock, const NiceAddress *to,
Youness Alaoui's avatar
Youness Alaoui committed
488
    guint len, const gchar *buf)
489 490 491 492 493
{
  TurnPriv *priv = (TurnPriv *) sock->priv;
  StunMessage msg;
  uint8_t buffer[STUN_MAX_MESSAGE_SIZE];
  size_t msg_len;
494 495 496 497
  union {
    struct sockaddr_storage storage;
    struct sockaddr addr;
  } sa;
498 499
  GList *i = priv->channels;
  ChannelBinding *binding = NULL;
500

501 502 503 504 505 506
  for (; i; i = i->next) {
    ChannelBinding *b = i->data;
    if (nice_address_equal (&b->peer, to)) {
      binding = b;
      break;
    }
507
  }
508

509
  nice_address_copy_to_sockaddr (to, &sa.addr);
510

511
  if (binding) {
512 513
    if (priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_DRAFT9 ||
        priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_RFC5766) {
Youness Alaoui's avatar
Youness Alaoui committed
514 515 516 517 518 519 520 521 522 523
      if (len + sizeof(uint32_t) <= sizeof(buffer)) {
        uint16_t len16 = htons ((uint16_t) len);
        uint16_t channel16 = htons (binding->channel);
        memcpy (buffer, &channel16, sizeof(uint16_t));
        memcpy (buffer + sizeof(uint16_t), &len16,sizeof(uint16_t));
        memcpy (buffer + sizeof(uint32_t), buf, len);
        msg_len = len + sizeof(uint32_t);
      } else {
        return 0;
      }
524 525
    } else {
      return nice_socket_send (priv->base_socket, &priv->server_addr, len, buf);
526
    }
527
  } else {
528 529
    if (priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_DRAFT9 ||
        priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_RFC5766) {
530
      if (!stun_agent_init_indication (&priv->agent, &msg,
Youness Alaoui's avatar
Youness Alaoui committed
531
              buffer, sizeof(buffer), STUN_IND_SEND))
532 533
        goto send;
      if (stun_message_append_xor_addr (&msg, STUN_ATTRIBUTE_PEER_ADDRESS,
534
              &sa.addr, sizeof(sa)) !=
535
          STUN_MESSAGE_RETURN_SUCCESS)
536 537 538
        goto send;
    } else {
      if (!stun_agent_init_request (&priv->agent, &msg,
Youness Alaoui's avatar
Youness Alaoui committed
539
              buffer, sizeof(buffer), STUN_SEND))
540
        goto send;
541

542
      if (stun_message_append32 (&msg, STUN_ATTRIBUTE_MAGIC_COOKIE,
543
              TURN_MAGIC_COOKIE) != STUN_MESSAGE_RETURN_SUCCESS)
544 545 546
        goto send;
      if (priv->username != NULL && priv->username_len > 0) {
        if (stun_message_append_bytes (&msg, STUN_ATTRIBUTE_USERNAME,
Youness Alaoui's avatar
Youness Alaoui committed
547
                priv->username, priv->username_len) !=
548
            STUN_MESSAGE_RETURN_SUCCESS)
549
          goto send;
550
      }
551
      if (stun_message_append_addr (&msg, STUN_ATTRIBUTE_DESTINATION_ADDRESS,
552
              &sa.addr, sizeof(sa)) !=
553
          STUN_MESSAGE_RETURN_SUCCESS)
554 555 556 557 558 559
        goto send;

      if (priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_GOOGLE &&
          priv->current_binding &&
          nice_address_equal (&priv->current_binding->peer, to)) {
        stun_message_append32 (&msg, STUN_ATTRIBUTE_OPTIONS, 1);
560
      }
561 562
    }

563 564 565 566 567 568 569 570 571 572
    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);
    }

573
    if (stun_message_append_bytes (&msg, STUN_ATTRIBUTE_DATA,
Youness Alaoui's avatar
Youness Alaoui committed
574
            buf, len) != STUN_MESSAGE_RETURN_SUCCESS)
575
      goto send;
576

577
    msg_len = stun_agent_finish_message (&priv->agent, &msg,
Youness Alaoui's avatar
Youness Alaoui committed
578
        priv->password, priv->password_len);
579 580 581 582 583
    if (msg_len > 0 && stun_message_get_class (&msg) == STUN_REQUEST) {
      SendRequest *req = g_slice_new0 (SendRequest);

      req->priv = priv;
      stun_message_id (&msg, req->id);
584
      req->source = priv_timeout_add_with_context (priv,
585
          STUN_END_TIMEOUT, priv_forget_send_request, req);
586
      g_queue_push_tail (priv->send_requests, req);
587
    }
588 589
  }

590
  if (msg_len > 0) {
591
    if (priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_RFC5766 &&
Marcus Lundblad's avatar
Marcus Lundblad committed
592
        !priv_has_permission_for_peer (priv, to)) {
593
      if (!priv_has_sent_permission_for_peer (priv, to)) {
594
        priv_send_create_permission (priv, NULL, to);
595 596
      }

597
      /* enque data */
598
      nice_debug ("enqueuing data");
599 600 601 602
      socket_enqueue_data(priv, to, msg_len, (gchar *)buffer);
      return TRUE;
    } else {
      return nice_socket_send (priv->base_socket, &priv->server_addr,
Youness Alaoui's avatar
Youness Alaoui committed
603
          msg_len, (gchar *)buffer);
604
    }
605 606
  }
 send:
607
  return nice_socket_send (priv->base_socket, to, len, buf);
608
}
609 610

static gboolean
611
socket_is_reliable (NiceSocket *sock)
612
{
613 614
  TurnPriv *priv = (TurnPriv *) sock->priv;
  return nice_socket_is_reliable (priv->base_socket);
615 616
}

617 618 619 620
static gboolean
priv_forget_send_request (gpointer pointer)
{
  SendRequest *req = pointer;
621

622
  agent_lock ();
623

624 625
  if (g_source_is_destroyed (g_main_current_source ())) {
    nice_debug ("Source was destroyed. "
Youness Alaoui's avatar
Youness Alaoui committed
626
        "Avoided race condition in turn.c:priv_forget_send_request");
627 628 629
    agent_unlock ();
    return FALSE;
  }
630

631
  stun_agent_forget_transaction (&req->priv->agent, req->id);
632

633
  g_queue_remove (req->priv->send_requests, req);
634

635 636 637
  g_source_destroy (req->source);
  g_source_unref (req->source);
  req->source = NULL;
638

639
  agent_unlock ();
640

641
  g_slice_free (SendRequest, req);
642

643 644
  return FALSE;
}
645

646 647 648
static gboolean
priv_permission_timeout (gpointer data)
{
649 650 651
  TurnPriv *priv = (TurnPriv *) data;

  nice_debug ("Permission is about to timeout, schedule renewal");
652

653 654 655
  agent_lock ();
  /* remove all permissions for this agent (the permission for the peer
     we are sending to will be renewed) */
656
  priv_clear_permissions (priv);
657
  agent_unlock ();
Youness Alaoui's avatar
Youness Alaoui committed
658

659
  return TRUE;
660 661
}

662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685
static gboolean
priv_binding_expired_timeout (gpointer data)
{
  TurnPriv *priv = (TurnPriv *) data;
  GList *i;
  GSource *source = NULL;

  nice_debug ("Permission expired, refresh failed");

  agent_lock ();

  source = g_main_current_source ();
  if (g_source_is_destroyed (source)) {
    nice_debug ("Source was destroyed. "
        "Avoided race condition in turn.c:priv_binding_expired_timeout");
    agent_unlock ();
    return FALSE;
  }

  /* find current binding and destroy it */
  for (i = priv->channels ; i; i = i->next) {
    ChannelBinding *b = i->data;
    if (b->timeout_source == g_source_get_id (source)) {
      priv->channels = g_list_remove (priv->channels, b);
686 687
      /* Make sure we don't free a currently being-refreshed binding */
      if (priv->current_binding_msg && !priv->current_binding) {
688 689 690 691
        union {
          struct sockaddr_storage storage;
          struct sockaddr addr;
        } sa;
692 693 694 695 696 697
        socklen_t sa_len = sizeof(sa);
        NiceAddress to;

        /* look up binding associated with peer */
        stun_message_find_xor_addr (
            &priv->current_binding_msg->message,
698 699
            STUN_ATTRIBUTE_XOR_PEER_ADDRESS, &sa.addr, &sa_len);
        nice_address_set_from_sockaddr (&to, &sa.addr);
700 701 702 703 704 705 706 707 708

        /* 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;
        }
      }
709 710 711
      /* In case the binding timed out before it could be processed, add it to
         the pending list */
      priv_add_channel_binding (priv, &b->peer);
712 713 714 715 716 717 718 719 720 721
      g_free (b);
      break;
    }
  }

  agent_unlock ();

  return FALSE;
}

722 723 724
static gboolean
priv_binding_timeout (gpointer data)
{
725
  TurnPriv *priv = (TurnPriv *) data;
726
  GList *i;
727
  GSource *source = NULL;
Youness Alaoui's avatar
Youness Alaoui committed
728

729
  nice_debug ("Permission is about to timeout, sending binding renewal");
730

731
  agent_lock ();
732

733 734 735 736 737 738 739 740
  source = g_main_current_source ();
  if (g_source_is_destroyed (source)) {
    nice_debug ("Source was destroyed. "
        "Avoided race condition in turn.c:priv_binding_timeout");
    agent_unlock ();
    return FALSE;
  }

741
  /* find current binding and mark it for renewal */
742 743
  for (i = priv->channels ; i; i = i->next) {
    ChannelBinding *b = i->data;
744
    if (b->timeout_source == g_source_get_id (source)) {
745
      b->renew = TRUE;
746 747 748 749 750 751 752 753
      /* Install timer to expire the permission */
      b->timeout_source = g_timeout_add_seconds (STUN_EXPIRE_TIMEOUT,
              priv_binding_expired_timeout, priv);
      /* Send renewal */
      if (!priv->current_binding_msg)
        priv_send_channel_bind (priv, NULL, b->channel, &b->peer);
      break;
    }
754
  }
755

756
  agent_unlock ();
757

758
  return FALSE;
759
}
760

761
gint
762
nice_turn_socket_parse_recv (NiceSocket *sock, NiceSocket **from_sock,
Youness Alaoui's avatar
Youness Alaoui committed
763
    NiceAddress *from, guint len, gchar *buf,
764
    NiceAddress *recv_from, gchar *_recv_buf, guint recv_len)
765
{
766

767
  TurnPriv *priv = (TurnPriv *) sock->priv;
768 769
  StunValidationStatus valid;
  StunMessage msg;
Philip Withnall's avatar
Philip Withnall committed
770
  GList *l;
771
  ChannelBinding *binding = NULL;
772

773 774 775 776 777 778 779
  union {
    guint8 *u8;
    guint16 *u16;
  } recv_buf;

  recv_buf.u8 = (guint8 *) _recv_buf;

780 781
  if (nice_address_equal (&priv->server_addr, recv_from)) {
    valid = stun_agent_validate (&priv->agent, &msg,
782
        recv_buf.u8, (size_t) recv_len, NULL, NULL);
783

784
    if (valid == STUN_VALIDATION_SUCCESS) {
785 786
      if (priv->compatibility != NICE_TURN_SOCKET_COMPATIBILITY_DRAFT9 &&
          priv->compatibility != NICE_TURN_SOCKET_COMPATIBILITY_RFC5766) {
787
        uint32_t cookie;
788
        if (stun_message_find32 (&msg, STUN_ATTRIBUTE_MAGIC_COOKIE,
Youness Alaoui's avatar
Youness Alaoui committed
789
                &cookie) != STUN_MESSAGE_RETURN_SUCCESS)
790 791 792 793
          goto recv;
        if (cookie != TURN_MAGIC_COOKIE)
          goto recv;
      }
794

795
      if (stun_message_get_method (&msg) == STUN_SEND) {
796 797
        if (stun_message_get_class (&msg) == STUN_RESPONSE) {
          SendRequest *req = NULL;
798
          GList *i = g_queue_peek_head_link (priv->send_requests);
799 800 801 802 803 804 805 806 807 808 809 810 811 812 813
          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_source_destroy (req->source);
            g_source_unref (req->source);
814
            req->source = NULL;
815

816
            g_queue_remove (priv->send_requests, req);
817

818
            g_slice_free (SendRequest, req);
819 820 821 822 823 824 825 826
          }

          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;
          }
827 828
        }
        return 0;
829
      } else if (stun_message_get_method (&msg) == STUN_OLD_SET_ACTIVE_DST) {
830 831
        StunTransactionId request_id;
        StunTransactionId response_id;
Youness Alaoui's avatar
Youness Alaoui committed
832

833 834 835
        if (priv->current_binding && priv->current_binding_msg) {
          stun_message_id (&msg, response_id);
          stun_message_id (&priv->current_binding_msg->message, request_id);
836
          if (memcmp (request_id, response_id,
Youness Alaoui's avatar
Youness Alaoui committed
837
                  sizeof(StunTransactionId)) == 0) {
838 839 840 841
            g_free (priv->current_binding_msg);
            priv->current_binding_msg = NULL;

            if (stun_message_get_class (&msg) == STUN_RESPONSE &&
842 843
                (priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_OC2007 ||
                 priv->compatibility == NICE_TURN_SOCKET_COMPATIBILITY_MSN)) {
844 845 846 847 848 849
              goto msn_google_lock;
            } else {
              g_free (priv->current_binding);
              priv->current_binding = NULL;
            }
          }
850
        }
851

852
        return 0;
853
      } else if (stun_message_get_method (&msg) == STUN_CHANNELBIND) {
854 855
        StunTransactionId request_id;
        StunTransactionId response_id;
856

857
        if (priv->current_binding_msg) {
858 859
          stun_message_id (&msg, response_id);
          stun_message_id (&priv->current_binding_msg->message, request_id);
860
          if (memcmp (request_id, response_id,
Youness Alaoui's avatar
Youness Alaoui committed
861
                  sizeof(StunTransactionId)) == 0) {
862 863 864 865 866 867 868

            if (priv->current_binding) {
              /* New channel binding */
              binding = priv->current_binding;
            } else {
              /* Existing binding refresh */
              GList *i;
869 870 871 872
              union {
                struct sockaddr_storage storage;
                struct sockaddr addr;
              } sa;
873 874 875 876 877 878
              socklen_t sa_len = sizeof(sa);
              NiceAddress to;

              /* look up binding associated with peer */
              stun_message_find_xor_addr (
                  &priv->current_binding_msg->message,
879 880
                  STUN_ATTRIBUTE_XOR_PEER_ADDRESS, &sa.addr, &sa_len);
              nice_address_set_from_sockaddr (&to, &sa.addr);
881 882 883 884 885 886 887 888 889 890

              for (i = priv->channels; i; i = i->next) {
                ChannelBinding *b = i->data;
                if (nice_address_equal (&b->peer, &to)) {
                  binding = b;
                  break;
                }
              }
            }

Marcus Lundblad's avatar
Marcus Lundblad committed
891
            if (stun_message_get_class (&msg) == STUN_ERROR) {
892
              int code = -1;
Marcus Lundblad's avatar
Marcus Lundblad committed
893
              uint8_t *sent_realm = NULL;
894 895 896
              uint8_t *recv_realm = NULL;
              uint16_t sent_realm_len = 0;
              uint16_t recv_realm_len = 0;
897 898

              sent_realm =
Youness Alaoui's avatar
Youness Alaoui committed
899 900 901
                  (uint8_t *) stun_message_find (
                      &priv->current_binding_msg->message,
                      STUN_ATTRIBUTE_REALM, &sent_realm_len);
902
              recv_realm =
Youness Alaoui's avatar
Youness Alaoui committed
903 904
                  (uint8_t *) stun_message_find (&msg,
                      STUN_ATTRIBUTE_REALM, &recv_realm_len);
905

906
              /* check for unauthorized error response */
907 908
              if (stun_message_find_error (&msg, &code) ==
                  STUN_MESSAGE_RETURN_SUCCESS &&
909
                  (code == 438 || (code == 401 &&
Youness Alaoui's avatar
Youness Alaoui committed
910 911 912 913 914 915
                      !(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)))) {
916

917 918 919 920 921
                g_free (priv->current_binding_msg);
                priv->current_binding_msg = NULL;
                if (binding)
                  priv_send_channel_bind (priv, &msg, binding->channel,
                      &binding->peer);
Marcus Lundblad's avatar
Marcus Lundblad committed
922
              } else {
923 924 925 926 927 928 929 930 931
                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;
Marcus Lundblad's avatar
Marcus Lundblad committed
932

933 934
              /* If it's a new channel binding, then add it to the list */
              if (priv->current_binding)
935
                priv->channels = g_list_append (priv->channels,
Youness Alaoui's avatar
Youness Alaoui committed
936
                    priv->current_binding);
937 938 939
              priv->current_binding = NULL;

              if (binding) {
940
                binding->renew = FALSE;
941

942 943 944 945 946
                /* Remove any existing timer */
                if (binding->timeout_source)
                  g_source_remove (binding->timeout_source);
                /* Install timer to schedule refresh of the permission */
                binding->timeout_source =
Youness Alaoui's avatar
Youness Alaoui committed
947 948
                    g_timeout_add_seconds (STUN_BINDING_TIMEOUT,
                        priv_binding_timeout, priv);
Marcus Lundblad's avatar
Marcus Lundblad committed
949
              }
950
              priv_process_pending_bindings (priv);
951 952
            }
          }
953
        }
954
        return 0;
955
      } else if (stun_message_get_method (&msg) == STUN_CREATEPERMISSION) {
Marcus Lundblad's avatar
Marcus Lundblad committed
956
        StunTransactionId request_id;
957
        StunTransactionId response_id;
958 959 960 961 962 963
        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;
964

Marcus Lundblad's avatar
Marcus Lundblad committed
965
          stun_message_id (&msg, response_id);
966
          stun_message_id (&current_create_permission_msg->message, request_id);
Marcus Lundblad's avatar
Marcus Lundblad committed
967

968 969
          if (memcmp (request_id, response_id,
                  sizeof(StunTransactionId)) == 0) {
970 971 972 973
            union {
              struct sockaddr_storage storage;
              struct sockaddr addr;
            } peer;
Marcus Lundblad's avatar
Marcus Lundblad committed
974
            socklen_t peer_len = sizeof(peer);
975
            NiceAddress to;
Marcus Lundblad's avatar
Marcus Lundblad committed
976

977
            nice_debug ("got response for CreatePermission");
978
            stun_message_find_xor_addr (
979
                &current_create_permission_msg->message,
980 981
                STUN_ATTRIBUTE_XOR_PEER_ADDRESS, &peer.addr, &peer_len);
            nice_address_set_from_sockaddr (&to, &peer.addr);
Marcus Lundblad's avatar
Marcus Lundblad committed
982

983 984 985 986
            /* unathorized => resend with realm and nonce */
            if (stun_message_get_class (&msg) == STUN_ERROR) {
              int code = -1;
              uint8_t *sent_realm = NULL;
Marcus Lundblad's avatar
Marcus Lundblad committed
987
              uint8_t *recv_realm = NULL;
988
              uint16_t sent_realm_len = 0;
Marcus Lundblad's avatar
Marcus Lundblad committed
989 990
              uint16_t recv_realm_len = 0;

991 992
              sent_realm =
                  (uint8_t *) stun_message_find (
993
                      &current_create_permission_msg->message,
994 995 996 997
                      STUN_ATTRIBUTE_REALM, &sent_realm_len);
              recv_realm =
                  (uint8_t *) stun_message_find (&msg,
                      STUN_ATTRIBUTE_REALM, &recv_realm_len);
Marcus Lundblad's avatar
Marcus Lundblad committed
998