conncheck.c 115 KB
Newer Older
1 2 3
/*
 * This file is part of the Nice GLib ICE library.
 *
4 5 6
 * (C) 2006-2009 Collabora Ltd.
 *  Contact: Youness Alaoui
 * (C) 2006-2009 Nokia Corporation. All rights reserved.
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
 *  Contact: Kai Vehmanen
 *
 * 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:
 *   Kai Vehmanen, Nokia
26
 *   Youness Alaoui, Collabora Ltd.
27 28 29 30 31 32 33 34 35 36 37 38 39
 *   Dafydd Harries, 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.
 */

40
/*
41 42 43 44 45 46 47 48
 * @file conncheck.c
 * @brief ICE connectivity checks
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

49 50 51 52 53
#include <errno.h>
#include <string.h>

#include <glib.h>

54 55
#include "debug.h"

56 57 58 59
#include "agent.h"
#include "agent-priv.h"
#include "conncheck.h"
#include "discovery.h"
60
#include "stun/usages/ice.h"
61 62
#include "stun/usages/bind.h"
#include "stun/usages/turn.h"
63

64
static void priv_update_check_list_failed_components (NiceAgent *agent, Stream *stream);
65
static void priv_update_check_list_state_for_ready (NiceAgent *agent, Stream *stream, Component *component);
66
static guint priv_prune_pending_checks (Stream *stream, guint component_id);
67
static gboolean priv_schedule_triggered_check (NiceAgent *agent, Stream *stream, Component *component, NiceSocket *local_socket, NiceCandidate *remote_cand, gboolean use_candidate);
68
static void priv_mark_pair_nominated (NiceAgent *agent, Stream *stream, Component *component, NiceCandidate *remotecand);
69 70 71 72 73
static size_t priv_create_username (NiceAgent *agent, Stream *stream,
    guint component_id, NiceCandidate *remote, NiceCandidate *local,
    uint8_t *dest, guint dest_len, gboolean inbound);
static size_t priv_get_password (NiceAgent *agent, Stream *stream,
    NiceCandidate *remote, uint8_t **password);
74
static void conn_check_free_item (gpointer data);
75
static void priv_conn_check_add_for_candidate_pair_matched (NiceAgent *agent, guint stream_id, Component *component, NiceCandidate *local, NiceCandidate *remote, NiceCheckState initial_state);
76

77
static int priv_timer_expired (GTimeVal *timer, GTimeVal *now)
78 79 80 81 82
{
  return (now->tv_sec == timer->tv_sec) ?
    now->tv_usec >= timer->tv_usec :
    now->tv_sec >= timer->tv_sec;
}
83

84
/*
85 86 87 88 89 90
 * Finds the next connectivity check in WAITING state.
 */
static CandidateCheckPair *priv_conn_check_find_next_waiting (GSList *conn_check_list)
{
  GSList *i;

91 92
  /* note: list is sorted in priority order to first waiting check has
   *       the highest priority */
93 94 95 96 97 98 99 100 101 102

  for (i = conn_check_list; i ; i = i->next) {
    CandidateCheckPair *p = i->data;
    if (p->state == NICE_CHECK_WAITING)
      return p;
  }

  return NULL;
}

103
/*
104 105 106 107 108 109
 * Initiates a new connectivity check for a ICE candidate pair.
 *
 * @return TRUE on success, FALSE on error
 */
static gboolean priv_conn_check_initiate (NiceAgent *agent, CandidateCheckPair *pair)
{
110 111 112 113
  /* XXX: from ID-16 onwards, the checks should not be sent
   * immediately, but be put into the "triggered queue",
   * see  "7.2.1.4 Triggered Checks"
   */
114
  g_get_current_time (&pair->next_tick);
115
  g_time_val_add (&pair->next_tick, agent->timer_ta * 1000);
116
  pair->state = NICE_CHECK_IN_PROGRESS;
117
  nice_debug ("Agent %p : pair %p state IN_PROGRESS", agent, pair);
118 119 120 121
  conn_check_send (agent, pair);
  return TRUE;
}

122
/*
123
 * Unfreezes the next connectivity check in the list. Follows the
124
 * algorithm (2.) defined in 5.7.4 (Computing States) of the ICE spec
125
 * (ID-19), with some exceptions (see comments in code).
126 127 128
 *
 * See also sect 7.1.2.2.3 (Updating Pair States), and
 * priv_conn_check_unfreeze_related().
129 130 131
 * 
 * @return TRUE on success, and FALSE if no frozen candidates were found.
 */
132
static gboolean priv_conn_check_unfreeze_next (NiceAgent *agent)
133 134
{
  CandidateCheckPair *pair = NULL;
135
  GSList *i, *j;
136

137 138 139 140 141 142 143
  /* XXX: the unfreezing is implemented a bit differently than in the
   *      current ICE spec, but should still be interoperate:
   *   - checks are not grouped by foundation
   *   - one frozen check is unfrozen (lowest component-id, highest
   *     priority)
   */

144 145 146 147
  for (i = agent->streams; i; i = i->next) {
    Stream *stream = i->data;
    guint64 max_frozen_priority = 0;

148

149 150 151 152 153 154 155 156 157 158
    for (j = stream->conncheck_list; j ; j = j->next) {
      CandidateCheckPair *p = j->data;

      /* XXX: the prio check could be removed as the pairs are sorted
       *       already */

      if (p->state == NICE_CHECK_FROZEN) {
	if (p->priority > max_frozen_priority) {
	  max_frozen_priority = p->priority;
	  pair = p;
159
	}
160 161
      }
    }
162 163 164

    if (pair) 
      break;
165 166 167
  }
  
  if (pair) {
168
    nice_debug ("Agent %p : Pair %p with s/c-id %u/%u (%s) unfrozen.", agent, pair, pair->stream_id, pair->component_id, pair->foundation);
169
    pair->state = NICE_CHECK_WAITING;
170
    nice_debug ("Agent %p : pair %p state WAITING", agent, pair);
171 172 173 174 175 176
    return TRUE;
  }

  return FALSE;
}

177
/*
178
 * Unfreezes the next next connectivity check in the list after
Youness Alaoui's avatar
Youness Alaoui committed
179
 * check 'success_check' has successfully completed.
180
 *
181
 * See sect 7.1.2.2.3 (Updating Pair States) of ICE spec (ID-19).
182 183 184 185 186 187
 * 
 * @param agent context
 * @param ok_check a connectivity check that has just completed
 *
 * @return TRUE on success, and FALSE if no frozen candidates were found.
 */
188
static void priv_conn_check_unfreeze_related (NiceAgent *agent, Stream *stream, CandidateCheckPair *ok_check)
189 190 191 192 193 194
{
  GSList *i, *j;
  guint unfrozen = 0;

  g_assert (ok_check);
  g_assert (ok_check->state == NICE_CHECK_SUCCEEDED);
195 196
  g_assert (stream);
  g_assert (stream->id == ok_check->stream_id);
197 198

  /* step: perform the step (1) of 'Updating Pair States' */
199
  for (i = stream->conncheck_list; i ; i = i->next) {
200 201 202 203 204
    CandidateCheckPair *p = i->data;
   
    if (p->stream_id == ok_check->stream_id) {
      if (p->state == NICE_CHECK_FROZEN &&
	  strcmp (p->foundation, ok_check->foundation) == 0) {
Youness Alaoui's avatar
Youness Alaoui committed
205
	nice_debug ("Agent %p : Unfreezing check %p (after successful check %p).", agent, p, ok_check);
206
	p->state = NICE_CHECK_WAITING;
207
        nice_debug ("Agent %p : pair %p state WAITING", agent, p);
208
	++unfrozen;
209 210 211 212 213 214 215 216 217 218
      }
    }
  }

  /* step: perform the step (2) of 'Updating Pair States' */
  stream = agent_find_stream (agent, ok_check->stream_id);
  if (stream_all_components_ready (stream)) {
    /* step: unfreeze checks from other streams */
    for (i = agent->streams; i ; i = i->next) {
      Stream *s = i->data;
219
      for (j = stream->conncheck_list; j ; j = j->next) {
220 221 222 223 224 225
	CandidateCheckPair *p = j->data;

	if (p->stream_id == s->id &&
	    p->stream_id != ok_check->stream_id) {
	  if (p->state == NICE_CHECK_FROZEN &&
	      strcmp (p->foundation, ok_check->foundation) == 0) {
Youness Alaoui's avatar
Youness Alaoui committed
226
	    nice_debug ("Agent %p : Unfreezing check %p from stream %u (after successful check %p).", agent, p, s->id, ok_check);
227
	    p->state = NICE_CHECK_WAITING;
228
            nice_debug ("Agent %p : pair %p state WAITING", agent, p);
229 230 231 232 233 234 235 236 237 238 239 240
	    ++unfrozen;
					    
	  }
	}
      }
      /* note: only unfreeze check from one stream at a time */
      if (unfrozen)
	break;
    }
  }    

  if (unfrozen == 0) 
241
    priv_conn_check_unfreeze_next (agent);
242 243
}

244 245 246 247 248 249 250 251 252 253 254
static void
candidate_check_pair_fail (Stream *stream, NiceAgent *agent, CandidateCheckPair *p)
{
  StunTransactionId id;
  Component *component;

  component = stream_find_component_by_id (stream, p->component_id);

  p->state = NICE_CHECK_FAILED;
  nice_debug ("Agent %p : pair %p state FAILED", agent, p);

255 256 257 258
  if (p->stun_message.buffer != NULL) {
    stun_message_id (&p->stun_message, id);
    stun_agent_forget_transaction (&component->stun_agent, id);
  }
259 260 261 262 263

  p->stun_message.buffer = NULL;
  p->stun_message.buffer_len = 0;
}

264
/*
265 266 267 268 269 270 271 272 273 274
 * Helper function for connectivity check timer callback that
 * runs through the stream specific part of the state machine. 
 *
 * @param schedule if TRUE, schedule a new check
 *
 * @return will return FALSE when no more pending timers.
 */
static gboolean priv_conn_check_tick_stream (Stream *stream, NiceAgent *agent, GTimeVal *now)
{
  gboolean keep_timer_going = FALSE;
275 276
  guint s_inprogress = 0, s_succeeded = 0, s_discovered = 0,
      s_nominated = 0, s_waiting_for_nomination = 0;
277 278 279 280 281
  guint frozen = 0, waiting = 0;
  GSList *i, *k;

  for (i = stream->conncheck_list; i ; i = i->next) {
    CandidateCheckPair *p = i->data;
282

283
    if (p->state == NICE_CHECK_IN_PROGRESS) {
284
      if (p->stun_message.buffer == NULL) {
285
	nice_debug ("Agent %p : STUN connectivity check was cancelled, marking as done.", agent);
286
	p->state = NICE_CHECK_FAILED;
287
        nice_debug ("Agent %p : pair %p state FAILED", agent, p);
288
      } else if (priv_timer_expired (&p->next_tick, now)) {
289 290
        switch (stun_timer_refresh (&p->timer)) {
          case STUN_USAGE_TIMER_RETURN_TIMEOUT:
291 292 293
            {
              /* case: error, abort processing */
              nice_debug ("Agent %p : Retransmissions failed, giving up on connectivity check %p", agent, p);
294
              candidate_check_pair_fail (stream, agent, p);
295 296 297

              break;
            }
298
          case STUN_USAGE_TIMER_RETURN_RETRANSMIT:
299 300 301
            {
              /* case: not ready, so schedule a new timeout */
              unsigned int timeout = stun_timer_remainder (&p->timer);
302
              nice_debug ("Agent %p :STUN transaction retransmitted (timeout %dms).",
303 304
                  agent, timeout);

305
              agent_socket_send (p->sockptr, &p->remote->addr,
306 307 308 309 310 311 312 313
                  stun_message_length (&p->stun_message),
                  (gchar *)p->stun_buffer);


              /* note: convert from milli to microseconds for g_time_val_add() */
              p->next_tick = *now;
              g_time_val_add (&p->next_tick, timeout * 1000);

Youness Alaoui's avatar
Youness Alaoui committed
314 315 316
              keep_timer_going = TRUE;
              break;
            }
317
          case STUN_USAGE_TIMER_RETURN_SUCCESS:
Youness Alaoui's avatar
Youness Alaoui committed
318
            {
319 320
              unsigned int timeout = stun_timer_remainder (&p->timer);

Youness Alaoui's avatar
Youness Alaoui committed
321 322 323 324
              /* note: convert from milli to microseconds for g_time_val_add() */
              p->next_tick = *now;
              g_time_val_add (&p->next_tick, timeout * 1000);

325 326 327
              keep_timer_going = TRUE;
              break;
            }
328 329 330
          default:
            /* Nothing to do. */
            break;
331
        }
332 333
      }
    }
334

335 336 337 338 339 340 341 342
    if (p->state == NICE_CHECK_FROZEN)
      ++frozen;
    else if (p->state == NICE_CHECK_IN_PROGRESS)
      ++s_inprogress;
    else if (p->state == NICE_CHECK_WAITING)
      ++waiting;
    else if (p->state == NICE_CHECK_SUCCEEDED)
      ++s_succeeded;
343 344
    else if (p->state == NICE_CHECK_DISCOVERED)
      ++s_discovered;
345

346 347
    if ((p->state == NICE_CHECK_SUCCEEDED || p->state == NICE_CHECK_DISCOVERED)
        && p->nominated)
348
      ++s_nominated;
349 350
    else if ((p->state == NICE_CHECK_SUCCEEDED ||
            p->state == NICE_CHECK_DISCOVERED) && !p->nominated)
351
      ++s_waiting_for_nomination;
352
  }
353

354 355 356 357 358 359 360 361 362 363
    /* note: keep the timer going as long as there is work to be done */
  if (s_inprogress)
    keep_timer_going = TRUE;
  
    /* note: if some components have established connectivity,
     *       but yet no nominated pair, keep timer going */
  if (s_nominated < stream->n_components &&
      s_waiting_for_nomination) {
    keep_timer_going = TRUE;
    if (agent->controlling_mode) {
364 365 366 367 368 369
      GSList *component_item;

      for (component_item = stream->components; component_item;
           component_item = component_item->next) {
        Component *component = component_item->data;

370 371 372
	for (k = stream->conncheck_list; k ; k = k->next) {
	  CandidateCheckPair *p = k->data;
	  /* note: highest priority item selected (list always sorted) */
373 374 375
	  if (p->component_id == component->id &&
              (p->state == NICE_CHECK_SUCCEEDED ||
               p->state == NICE_CHECK_DISCOVERED)) {
376
	    nice_debug ("Agent %p : restarting check %p as the nominated pair.", agent, p);
377
	    p->nominated = TRUE;
378
	    priv_conn_check_initiate (agent, p);
379 380 381 382 383 384
	    break; /* move to the next component */
	  }
	}
      }
    }
  }
385
    {
386 387
    static int tick_counter = 0;
    if (tick_counter++ % 50 == 0 || keep_timer_going != TRUE)
388 389 390 391 392
      nice_debug ("Agent %p : timer tick #%u: %u frozen, %u in-progress, "
          "%u waiting, %u succeeded, %u discovered, %u nominated, "
          "%u waiting-for-nom.", agent,
          tick_counter, frozen, s_inprogress, waiting, s_succeeded,
          s_discovered, s_nominated, s_waiting_for_nomination);
393 394 395 396 397 398
  }

  return keep_timer_going;

}

Youness Alaoui's avatar
Youness Alaoui committed
399

400
/*
401 402 403 404 405 406 407
 * Timer callback that handles initiating and managing connectivity
 * checks (paced by the Ta timer).
 *
 * This function is designed for the g_timeout_add() interface.
 *
 * @return will return FALSE when no more pending timers.
 */
408
static gboolean priv_conn_check_tick_unlocked (NiceAgent *agent)
409 410 411
{
  CandidateCheckPair *pair = NULL;
  gboolean keep_timer_going = FALSE;
412
  GSList *i, *j;
413
  GTimeVal now;
414 415 416

  /* step: process ongoing STUN transactions */
  g_get_current_time (&now);
417

418 419 420
  /* step: find the highest priority waiting check and send it */
  for (i = agent->streams; i ; i = i->next) {
    Stream *stream = i->data;
421

422
    pair = priv_conn_check_find_next_waiting (stream->conncheck_list);
423 424 425
    if (pair)
      break;
  }
426 427 428 429

  if (pair) {
    priv_conn_check_initiate (agent, pair);
    keep_timer_going = TRUE;
430 431
  } else {
    keep_timer_going = priv_conn_check_unfreeze_next (agent);
432 433
  }

434 435
  for (j = agent->streams; j; j = j->next) {
    Stream *stream = j->data;
436 437 438 439
    gboolean res =
      priv_conn_check_tick_stream (stream, agent, &now);
    if (res)
      keep_timer_going = res;
440
  }
441

442
  /* step: stop timer if no work left */
443
  if (keep_timer_going != TRUE) {
444
    nice_debug ("Agent %p : %s: stopping conncheck timer", agent, G_STRFUNC);
445 446
    for (i = agent->streams; i; i = i->next) {
      Stream *stream = i->data;
447
      priv_update_check_list_failed_components (agent, stream);
448 449 450 451
      for (j = stream->components; j; j = j->next) {
        Component *component = j->data;
        priv_update_check_list_state_for_ready (agent, stream, component);
      }
452
    }
453 454 455 456 457 458 459 460 461 462

    /* Stopping the timer so destroy the source.. this will allow
       the timer to be reset if we get a set_remote_candidates after this
       point */
    if (agent->conncheck_timer_source != NULL) {
      g_source_destroy (agent->conncheck_timer_source);
      g_source_unref (agent->conncheck_timer_source);
      agent->conncheck_timer_source = NULL;
    }

463
    /* XXX: what to signal, is all processing now really done? */
464
    nice_debug ("Agent %p : changing conncheck state to COMPLETED.", agent);
465 466 467 468 469
  }

  return keep_timer_going;
}

Youness Alaoui's avatar
Youness Alaoui committed
470 471 472
static gboolean priv_conn_check_tick (gpointer pointer)
{
  gboolean ret;
473
  NiceAgent *agent = pointer;
Youness Alaoui's avatar
Youness Alaoui committed
474

475 476 477 478 479 480 481
  agent_lock();
  if (g_source_is_destroyed (g_main_current_source ())) {
    nice_debug ("Source was destroyed. "
        "Avoided race condition in priv_conn_check_tick");
    agent_unlock ();
    return FALSE;
  }
482 483 484

  ret = priv_conn_check_tick_unlocked (agent);
  agent_unlock_and_emit (agent);
Youness Alaoui's avatar
Youness Alaoui committed
485 486 487 488

  return ret;
}

489 490 491 492
static gboolean priv_conn_keepalive_retransmissions_tick (gpointer pointer)
{
  CandidatePair *pair = (CandidatePair *) pointer;

493
  agent_lock();
494

495 496 497 498
  /* A race condition might happen where the mutex above waits for the lock
   * and in the meantime another thread destroys the source.
   * In that case, we don't need to run our retransmission tick since it should
   * have been cancelled */
499 500 501 502
  if (g_source_is_destroyed (g_main_current_source ())) {
    nice_debug ("Source was destroyed. "
        "Avoided race condition in priv_conn_keepalive_retransmissions_tick");
    agent_unlock ();
503 504 505
    return FALSE;
  }

506 507 508 509 510 511
  g_source_destroy (pair->keepalive.tick_source);
  g_source_unref (pair->keepalive.tick_source);
  pair->keepalive.tick_source = NULL;

  switch (stun_timer_refresh (&pair->keepalive.timer)) {
    case STUN_USAGE_TIMER_RETURN_TIMEOUT:
512 513 514
      {
        /* Time out */
        StunTransactionId id;
515 516 517 518 519 520 521 522 523 524
        Component *component;

        if (!agent_find_component (pair->keepalive.agent,
                pair->keepalive.stream_id, pair->keepalive.component_id,
                NULL, &component)) {
          nice_debug ("Could not find stream or component in"
              " priv_conn_keepalive_retransmissions_tick");
          agent_unlock ();
          return FALSE;
        }
525 526

        stun_message_id (&pair->keepalive.stun_message, id);
527
        stun_agent_forget_transaction (&component->stun_agent, id);
528

529 530 531 532 533 534 535 536 537 538 539 540 541
        if (pair->keepalive.agent->media_after_tick) {
          nice_debug ("Agent %p : Keepalive conncheck timed out!! "
              "but media was received. Suspecting keepalive lost because of "
              "network bottleneck", pair->keepalive.agent);

          pair->keepalive.stun_message.buffer = NULL;
        } else {
          nice_debug ("Agent %p : Keepalive conncheck timed out!! "
              "peer probably lost connection", pair->keepalive.agent);
          agent_signal_component_state_change (pair->keepalive.agent,
              pair->keepalive.stream_id, pair->keepalive.component_id,
              NICE_COMPONENT_STATE_FAILED);
        }
542 543
        break;
      }
544 545
    case STUN_USAGE_TIMER_RETURN_RETRANSMIT:
      /* Retransmit */
546
      agent_socket_send (pair->local->sockptr, &pair->remote->addr,
547 548 549
          stun_message_length (&pair->keepalive.stun_message),
          (gchar *)pair->keepalive.stun_buffer);

550 551
      nice_debug ("Agent %p : Retransmitting keepalive conncheck",
          pair->keepalive.agent);
552 553 554 555
      agent_timeout_add_with_context (pair->keepalive.agent,
          &pair->keepalive.tick_source,
          "Pair keepalive", stun_timer_remainder (&pair->keepalive.timer),
          priv_conn_keepalive_retransmissions_tick, pair);
556 557
      break;
    case STUN_USAGE_TIMER_RETURN_SUCCESS:
558 559 560 561
      agent_timeout_add_with_context (pair->keepalive.agent,
          &pair->keepalive.tick_source,
          "Pair keepalive", stun_timer_remainder (&pair->keepalive.timer),
          priv_conn_keepalive_retransmissions_tick, pair);
562
      break;
563 564 565
    default:
      /* Nothing to do. */
      break;
566 567 568
  }


569
  agent_unlock_and_emit (pair->keepalive.agent);
570 571 572
  return FALSE;
}

573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599
static guint32 peer_reflexive_candidate_priority (NiceAgent *agent,
    NiceCandidate *local_candidate)
{
  NiceCandidate *candidate_priority =
      nice_candidate_new (NICE_CANDIDATE_TYPE_PEER_REFLEXIVE);
  guint32 priority;

  candidate_priority->transport = local_candidate->transport;
  candidate_priority->component_id = local_candidate->component_id;
  if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE) {
    priority = nice_candidate_jingle_priority (candidate_priority);
  } else if (agent->compatibility == NICE_COMPATIBILITY_MSN ||
             agent->compatibility == NICE_COMPATIBILITY_OC2007) {
    priority = nice_candidate_msn_priority (candidate_priority);
  } else if (agent->compatibility == NICE_COMPATIBILITY_OC2007R2) {
    priority = nice_candidate_ms_ice_priority (candidate_priority,
        agent->reliable, FALSE);
  } else {
    priority = nice_candidate_ice_priority (candidate_priority,
        agent->reliable, FALSE);
  }
  nice_candidate_free (candidate_priority);

  return priority;
}


600

601
/*
602 603 604 605 606 607 608
 * Timer callback that handles initiating and managing connectivity
 * checks (paced by the Ta timer).
 *
 * This function is designed for the g_timeout_add() interface.
 *
 * @return will return FALSE when no more pending timers.
 */
609
static gboolean priv_conn_keepalive_tick_unlocked (NiceAgent *agent)
610
{
611
  GSList *i, *j, *k;
612
  int errors = 0;
613
  gboolean ret = FALSE;
614
  size_t buf_len = 0;
615 616

  /* case 1: session established and media flowing
617
   *         (ref ICE sect 10 "Keepalives" ID-19)  */
618
  for (i = agent->streams; i; i = i->next) {
619

620
    Stream *stream = i->data;
621 622
    for (j = stream->components; j; j = j->next) {
      Component *component = j->data;
623
      if (component->selected_pair.local != NULL) {
624
	CandidatePair *p = &component->selected_pair;
Youness Alaoui's avatar
Youness Alaoui committed
625

626 627 628 629
        /* Disable keepalive checks on TCP candidates */
        if (p->local->transport != NICE_CANDIDATE_TRANSPORT_UDP)
          continue;

630 631
        if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE ||
            agent->keepalive_conncheck) {
632
          guint32 priority;
633 634 635 636 637 638 639 640 641
          uint8_t uname[NICE_STREAM_MAX_UNAME];
          size_t uname_len =
              priv_create_username (agent, agent_find_stream (agent, stream->id),
                  component->id, p->remote, p->local, uname, sizeof (uname),
                  FALSE);
          uint8_t *password = NULL;
          size_t password_len = priv_get_password (agent,
              agent_find_stream (agent, stream->id), p->remote, &password);

642
          priority = peer_reflexive_candidate_priority (agent, p->local);
643

644 645 646 647
          if (nice_debug_is_enabled ()) {
            gchar tmpbuf[INET6_ADDRSTRLEN];
            nice_address_to_string (&p->remote->addr, tmpbuf);
            nice_debug ("Agent %p : Keepalive STUN-CC REQ to '%s:%u', "
648 649
                "socket=%u (c-id:%u), username='%.*s' (%" G_GSIZE_FORMAT "), "
                "password='%.*s' (%" G_GSIZE_FORMAT "), priority=%u.", agent,
650 651
                tmpbuf, nice_address_get_port (&p->remote->addr),
                g_socket_get_fd(((NiceSocket *)p->local->sockptr)->fileno),
652 653
                component->id, (int) uname_len, uname, uname_len,
                (int) password_len, password, password_len, priority);
654
          }
655
          if (uname_len > 0) {
656
            buf_len = stun_usage_ice_conncheck_create (&component->stun_agent,
657 658 659 660 661
                &p->keepalive.stun_message, p->keepalive.stun_buffer,
                sizeof(p->keepalive.stun_buffer),
                uname, uname_len, password, password_len,
                agent->controlling_mode, agent->controlling_mode, priority,
                agent->tie_breaker,
Jakub Adam's avatar
Jakub Adam committed
662
                NULL,
663 664
                agent_to_ice_compatibility (agent));

665
            nice_debug ("Agent %p: conncheck created %zd - %p",
666 667
                agent, buf_len, p->keepalive.stun_message.buffer);

668
            if (buf_len > 0) {
669 670
              stun_timer_start (&p->keepalive.timer, STUN_TIMER_DEFAULT_TIMEOUT,
                  STUN_TIMER_DEFAULT_MAX_RETRANSMISSIONS);
671

672 673
              agent->media_after_tick = FALSE;

674
              /* send the conncheck */
675
              agent_socket_send (p->local->sockptr, &p->remote->addr,
676 677 678 679 680 681
                  buf_len, (gchar *)p->keepalive.stun_buffer);

              p->keepalive.stream_id = stream->id;
              p->keepalive.component_id = component->id;
              p->keepalive.agent = agent;

682 683 684 685
              agent_timeout_add_with_context (p->keepalive.agent,
                  &p->keepalive.tick_source, "Pair keepalive",
                  stun_timer_remainder (&p->keepalive.timer),
                  priv_conn_keepalive_retransmissions_tick, p);
686 687
            } else {
              ++errors;
688 689 690
            }
          }
        } else {
691
          buf_len = stun_usage_bind_keepalive (&component->stun_agent,
692 693 694
              &p->keepalive.stun_message, p->keepalive.stun_buffer,
              sizeof(p->keepalive.stun_buffer));

695
          if (buf_len > 0) {
696
            agent_socket_send (p->local->sockptr, &p->remote->addr, buf_len,
697
                (gchar *)p->keepalive.stun_buffer);
698

699 700 701
            nice_debug ("Agent %p : stun_bind_keepalive for pair %p res %d.",
                agent, p, (int) buf_len);
          } else {
702
            ++errors;
703
          }
704
        }
705
      }
706 707
    }
  }
Youness Alaoui's avatar
Youness Alaoui committed
708

709
  /* case 2: connectivity establishment ongoing
710
   *         (ref ICE sect 4.1.1.4 "Keeping Candidates Alive" ID-19)  */
711 712
  for (i = agent->streams; i; i = i->next) {
    Stream *stream = i->data;
713 714 715 716 717 718 719
    for (j = stream->components; j; j = j->next) {
      Component *component = j->data;
      if (component->state < NICE_COMPONENT_STATE_READY &&
          agent->stun_server_ip) {
        NiceAddress stun_server;
        if (nice_address_set_from_string (&stun_server, agent->stun_server_ip)) {
          StunAgent stun_agent;
720
          uint8_t stun_buffer[STUN_MAX_MESSAGE_SIZE_IPV6];
721 722 723 724 725
          StunMessage stun_message;
          size_t buffer_len = 0;

          nice_address_set_port (&stun_server, agent->stun_server_port);

726 727 728 729
          /* FIXME: This will cause the stun response to arrive on the socket
           * but the stun agent will not be able to parse it due to an invalid
           * stun message since RFC3489 will not be compatible, and the response
           * will be forwarded to the application as user data */
730 731 732 733 734 735 736 737
          stun_agent_init (&stun_agent, STUN_ALL_KNOWN_ATTRIBUTES,
              STUN_COMPATIBILITY_RFC3489, 0);

          buffer_len = stun_usage_bind_create (&stun_agent,
              &stun_message, stun_buffer, sizeof(stun_buffer));

          for (k = component->local_candidates; k; k = k->next) {
            NiceCandidate *candidate = (NiceCandidate *) k->data;
738 739
            if (candidate->type == NICE_CANDIDATE_TYPE_HOST &&
                candidate->transport == NICE_CANDIDATE_TRANSPORT_UDP) {
740 741 742
              /* send the conncheck */
              nice_debug ("Agent %p : resending STUN on %s to keep the "
                  "candidate alive.", agent, candidate->foundation);
743
              agent_socket_send (candidate->sockptr, &stun_server,
744 745 746 747
                  buffer_len, (gchar *)stun_buffer);
            }
          }
        }
748 749 750
      }
    }
  }
Youness Alaoui's avatar
Youness Alaoui committed
751

752
  if (errors) {
753
    nice_debug ("Agent %p : %s: stopping keepalive timer", agent, G_STRFUNC);
754
    goto done;
755 756
  }

757 758 759
  ret = TRUE;

 done:
760 761 762 763 764 765 766 767
  return ret;
}

static gboolean priv_conn_keepalive_tick (gpointer pointer)
{
  NiceAgent *agent = pointer;
  gboolean ret;

768 769 770 771 772 773 774 775
  agent_lock();
  if (g_source_is_destroyed (g_main_current_source ())) {
    nice_debug ("Source was destroyed. "
        "Avoided race condition in priv_conn_keepalive_tick");
    agent_unlock ();
    return FALSE;
  }

776
  ret = priv_conn_keepalive_tick_unlocked (agent);
777 778 779 780 781 782 783
  if (ret == FALSE) {
    if (agent->keepalive_timer_source) {
      g_source_destroy (agent->keepalive_timer_source);
      g_source_unref (agent->keepalive_timer_source);
      agent->keepalive_timer_source = NULL;
    }
  }
784
  agent_unlock_and_emit (agent);
785
  return ret;
786 787
}

788

789 790 791
static gboolean priv_turn_allocate_refresh_retransmissions_tick (gpointer pointer)
{
  CandidateRefresh *cand = (CandidateRefresh *) pointer;
792
  NiceAgent *agent = NULL;
793

794
  agent_lock();
795

796 797 798 799
  /* A race condition might happen where the mutex above waits for the lock
   * and in the meantime another thread destroys the source.
   * In that case, we don't need to run our retransmission tick since it should
   * have been cancelled */
800 801 802 803
  if (g_source_is_destroyed (g_main_current_source ())) {
    nice_debug ("Source was destroyed. "
        "Avoided race condition in priv_turn_allocate_refresh_retransmissions_tick");
    agent_unlock ();
804 805 806
    return FALSE;
  }

807

808 809 810 811
  g_source_destroy (cand->tick_source);
  g_source_unref (cand->tick_source);
  cand->tick_source = NULL;

812 813
  agent = g_object_ref (cand->agent);

814 815
  switch (stun_timer_refresh (&cand->timer)) {
    case STUN_USAGE_TIMER_RETURN_TIMEOUT:
816 817 818 819 820 821 822 823 824 825
      {
        /* Time out */
        StunTransactionId id;

        stun_message_id (&cand->stun_message, id);
        stun_agent_forget_transaction (&cand->stun_agent, id);

        refresh_cancel (cand);
        break;
      }
826
    case STUN_USAGE_TIMER_RETURN_RETRANSMIT:
827
      /* Retransmit */
828
      agent_socket_send (cand->nicesock, &cand->server,
829 830
          stun_message_length (&cand->stun_message), (gchar *)cand->stun_buffer);

831
      agent_timeout_add_with_context (agent, &cand->tick_source,
832
          "Candidate TURN refresh", stun_timer_remainder (&cand->timer),
833 834
          priv_turn_allocate_refresh_retransmissions_tick, cand);
      break;
835
    case STUN_USAGE_TIMER_RETURN_SUCCESS:
836
      agent_timeout_add_with_context (agent, &cand->tick_source,
837
          "Candidate TURN refresh", stun_timer_remainder (&cand->timer),
838 839
          priv_turn_allocate_refresh_retransmissions_tick, cand);
      break;
840 841 842
    default:
      /* Nothing to do. */
      break;
843 844 845
  }


846 847 848 849
  agent_unlock_and_emit (agent);

  g_object_unref (agent);

850 851 852 853 854 855
  return FALSE;
}

static void priv_turn_allocate_refresh_tick_unlocked (CandidateRefresh *cand)
{
  uint8_t *username;
856
  gsize username_len;
857
  uint8_t *password;
858
  gsize password_len;
859
  size_t buffer_len = 0;
860 861
  StunUsageTurnCompatibility turn_compat =
      agent_to_turn_compatibility (cand->agent);
862

863 864 865 866
  username = (uint8_t *)cand->candidate->turn->username;
  username_len = (size_t) strlen (cand->candidate->turn->username);
  password = (uint8_t *)cand->candidate->turn->password;
  password_len = (size_t) strlen (cand->candidate->turn->password);
867

868 869
  if (turn_compat == STUN_USAGE_TURN_COMPATIBILITY_MSN ||
      turn_compat == STUN_USAGE_TURN_COMPATIBILITY_OC2007) {
870 871 872 873
    username = g_base64_decode ((gchar *)username, &username_len);
    password = g_base64_decode ((gchar *)password, &password_len);
  }

874
  buffer_len = stun_usage_turn_create_refresh (&cand->stun_agent,
875 876 877 878
      &cand->stun_message,  cand->stun_buffer, sizeof(cand->stun_buffer),
      cand->stun_resp_msg.buffer == NULL ? NULL : &cand->stun_resp_msg, -1,
      username, username_len,
      password, password_len,
879
      turn_compat);
880

881 882
  if (turn_compat == STUN_USAGE_TURN_COMPATIBILITY_MSN ||
      turn_compat == STUN_USAGE_TURN_COMPATIBILITY_OC2007) {
883 884
    g_free (username);
    g_free (password);
885 886
  }

887 888
  nice_debug ("Agent %p : Sending allocate Refresh %zd", cand->agent,
      buffer_len);
889

890 891 892 893 894 895
  if (cand->tick_source != NULL) {
    g_source_destroy (cand->tick_source);
    g_source_unref (cand->tick_source);
    cand->tick_source = NULL;
  }

896
  if (buffer_len > 0) {
897 898
    stun_timer_start (&cand->timer, STUN_TIMER_DEFAULT_TIMEOUT,
        STUN_TIMER_DEFAULT_MAX_RETRANSMISSIONS);
899 900

    /* send the refresh */
901
    agent_socket_send (cand->nicesock, &cand->server,
902 903
        buffer_len, (gchar *)cand->stun_buffer);

904
    agent_timeout_add_with_context (cand->agent, &cand->tick_source,
905
        "Candidate TURN refresh", stun_timer_remainder (&cand->timer),
906 907 908 909 910 911
        priv_turn_allocate_refresh_retransmissions_tick, cand);
  }

}


912
/*
913 914 915 916 917 918 919 920 921 922
 * Timer callback that handles refreshing TURN allocations
 *
 * This function is designed for the g_timeout_add() interface.
 *
 * @return will return FALSE when no more pending timers.
 */
static gboolean priv_turn_allocate_refresh_tick (gpointer pointer)
{
  CandidateRefresh *cand = (CandidateRefresh *) pointer;

923 924 925 926 927 928 929 930
  agent_lock();
  if (g_source_is_destroyed (g_main_current_source ())) {
    nice_debug ("Source was destroyed. "
        "Avoided race condition in priv_turn_allocate_refresh_tick");
    agent_unlock ();
    return FALSE;
  }

931
  priv_turn_allocate_refresh_tick_unlocked (cand);
932
  agent_unlock_and_emit (cand->agent);
933 934 935 936 937

  return FALSE;
}


938
/*
939
 * Initiates the next pending connectivity check.
940 941
 * 
 * @return TRUE if a pending check was scheduled
942
 */
943
gboolean conn_check_schedule_next (NiceAgent *agent)
944
{
945
  gboolean res = priv_conn_check_unfreeze_next (agent);
946
  nice_debug ("Agent %p : priv_conn_check_unfreeze_next returned %d", agent, res);
947

948
  if (agent->discovery_unsched_items > 0)
949
    nice_debug ("Agent %p : WARN: starting conn checks before local candidate gathering is finished.", agent);
950

951
  /* step: call once imediately */
952
  res = priv_conn_check_tick_unlocked (agent);
953
  nice_debug ("Agent %p : priv_conn_check_tick_unlocked returned %d", agent, res);
954

955 956
  /* step: schedule timer if not running yet */
  if (res && agent->conncheck_timer_source == NULL) {
957 958 959
    agent_timeout_add_with_context (agent, &agent->conncheck_timer_source,
        "Connectivity check schedule", agent->timer_ta,
        priv_conn_check_tick, agent);
960
  }
961

962 963
  /* step: also start the keepalive timer */
  if (agent->keepalive_timer_source == NULL) {
964 965 966
    agent_timeout_add_with_context (agent, &agent->keepalive_timer_source,
        "Connectivity keepalive timeout", NICE_AGENT_TIMER_TR_DEFAULT,
        priv_conn_keepalive_tick, agent);
967
  }
968

969
  nice_debug ("Agent %p : conn_check_schedule_next returning %d", agent, res);
970
  return res;
971 972
}

973
/*
974 975 976 977 978 979 980 981 982 983 984 985 986
 * Compares two connectivity check items. Checkpairs are sorted
 * in descending priority order, with highest priority item at
 * the start of the list.
 */
gint conn_check_compare (const CandidateCheckPair *a, const CandidateCheckPair *b)
{
  if (a->priority > b->priority)
    return -1;
  else if (a->priority < b->priority)
    return 1;
  return 0;
}

987
/*
988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003
 * Preprocesses a new connectivity check by going through list 
 * of a any stored early incoming connectivity checks from 
 * the remote peer. If a matching incoming check has been already
 * received, update the state of the new outgoing check 'pair'.
 * 
 * @param agent context pointer
 * @param stream which stream (of the agent)
 * @param component pointer to component object to which 'pair'has been added
 * @param pair newly added connectivity check
 */
static void priv_preprocess_conn_check_pending_data (NiceAgent *agent, Stream *stream, Component *component, CandidateCheckPair *pair)
{
  GSList *i;
  for (i = component->incoming_checks; i; i = i->next) {
    IncomingCheck *icheck = i->data;
    if (nice_address_equal (&icheck->from, &pair->remote->addr) &&
1004
	icheck->local_socket == pair->sockptr) {
1005
      nice_debug ("Agent %p : Updating check %p with stored early-icheck %p, %p/%u/%u (agent/stream/component).", agent, pair, icheck, agent, stream->id, component->id);
1006 1007 1008 1009 1010 1011 1012
      if (icheck->use_candidate)
	priv_mark_pair_nominated (agent, stream, component, pair->remote);
      priv_schedule_triggered_check (agent, stream, component, icheck->local_socket, pair->remote, icheck->use_candidate);
    }
  }
}