static void nr_turn_client_connected_cb(NR_SOCKET s, int how, void *cb_arg) { int r, _status; nr_turn_client_ctx *ctx = (nr_turn_client_ctx *)cb_arg; /* Cancel the connection failure timer */ NR_async_timer_cancel(ctx->connected_timer_handle); ctx->connected_timer_handle=0; /* Assume we connected successfully */ if (ctx->state == NR_TURN_CLIENT_STATE_ALLOCATION_WAIT) { if ((r=nr_turn_stun_ctx_start(STAILQ_FIRST(&ctx->stun_ctxs)))) ABORT(r); ctx->state = NR_TURN_CLIENT_STATE_ALLOCATING; } else { ctx->state = NR_TURN_CLIENT_STATE_CONNECTED; } _status = 0; abort: if (_status) { nr_turn_client_failed(ctx); } }
int nr_turn_client_allocate(nr_turn_client_ctx *ctx, NR_async_cb finished_cb, void *cb_arg) { nr_turn_stun_ctx *stun = 0; int r,_status; if(ctx->state == NR_TURN_CLIENT_STATE_FAILED || ctx->state == NR_TURN_CLIENT_STATE_CANCELLED){ /* TURN TCP contexts can fail before we ever try to form an allocation, * since the TCP connection can fail. It is also conceivable that a TURN * TCP context could be cancelled before we are done forming all * allocations (although we do not do this at the time this code was * written) */ assert(ctx->turn_server_addr.protocol == IPPROTO_TCP); ABORT(R_NOT_FOUND); } assert(ctx->state == NR_TURN_CLIENT_STATE_INITTED); ctx->finished_cb=finished_cb; ctx->cb_arg=cb_arg; if ((r=nr_turn_stun_ctx_create(ctx, NR_TURN_CLIENT_MODE_ALLOCATE_REQUEST, nr_turn_client_allocate_cb, nr_turn_client_error_cb, &stun))) ABORT(r); stun->stun->params.allocate_request.lifetime_secs = TURN_LIFETIME_REQUEST_SECONDS; if (ctx->state == NR_TURN_CLIENT_STATE_INITTED) { if ((r=nr_turn_stun_ctx_start(stun))) ABORT(r); ctx->state = NR_TURN_CLIENT_STATE_ALLOCATING; } else { ABORT(R_ALREADY); } _status=0; abort: if (_status) { nr_turn_client_failed(ctx); } return(_status); }
int nr_turn_client_allocate(nr_turn_client_ctx *ctx, NR_async_cb finished_cb, void *cb_arg) { nr_turn_stun_ctx *stun = 0; int r,_status; assert(ctx->state == NR_TURN_CLIENT_STATE_INITTED || ctx->state == NR_TURN_CLIENT_STATE_CONNECTED); ctx->finished_cb=finished_cb; ctx->cb_arg=cb_arg; if ((r=nr_turn_stun_ctx_create(ctx, NR_TURN_CLIENT_MODE_ALLOCATE_REQUEST, nr_turn_client_allocate_cb, nr_turn_client_error_cb, &stun))) ABORT(r); stun->stun->params.allocate_request.lifetime_secs = TURN_LIFETIME_REQUEST_SECONDS; switch(ctx->state) { case NR_TURN_CLIENT_STATE_INITTED: /* We are waiting for connect before we can allocate */ ctx->state = NR_TURN_CLIENT_STATE_ALLOCATION_WAIT; break; case NR_TURN_CLIENT_STATE_CONNECTED: if ((r=nr_turn_stun_ctx_start(stun))) ABORT(r); ctx->state = NR_TURN_CLIENT_STATE_ALLOCATING; break; default: ABORT(R_ALREADY); } _status=0; abort: if (_status) { nr_turn_client_failed(ctx); } return(_status); }
static void nr_turn_client_refresh_timer_cb(NR_SOCKET s, int how, void *arg) { nr_turn_stun_ctx *ctx = (nr_turn_stun_ctx *)arg; int r,_status; r_log(NR_LOG_TURN, LOG_DEBUG, "TURN(%s): Refresh timer fired", ctx->tctx->label); ctx->tctx->refresh_timer_handle=0; if ((r=nr_turn_stun_ctx_start(ctx))) { ABORT(r); } _status=0; abort: if (_status) { nr_turn_client_failed(ctx->tctx); } return; }
/* The permissions model is as follows: - We keep a list of all the permissions we have ever requested along with when they were last established. - Whenever someone sends a packet, we automatically create/ refresh the permission. This means that permissions automatically time out if unused. */ int nr_turn_client_ensure_perm(nr_turn_client_ctx *ctx, nr_transport_addr *addr) { int r, _status; nr_turn_permission *perm = 0; UINT8 now; UINT8 turn_permission_refresh = (TURN_PERMISSION_LIFETIME_SECONDS - TURN_REFRESH_SLACK_SECONDS) * TURN_USECS_PER_S; if ((r=nr_turn_permission_find(ctx, addr, &perm))) { if (r == R_NOT_FOUND) { if ((r=nr_turn_permission_create(ctx, addr, &perm))) ABORT(r); } else { ABORT(r); } } assert(perm); /* Now check that the permission is up-to-date */ now = r_gettimeint(); if ((now - perm->last_used) > turn_permission_refresh) { r_log(NR_LOG_TURN, LOG_DEBUG, "TURN(%s): Permission for %s requires refresh", ctx->label, perm->addr.as_string); if ((r=nr_turn_stun_ctx_start(perm->stun))) ABORT(r); perm->last_used = now; /* Update the time now so we don't retry on next packet */ } _status=0; abort: return(_status); }
static void nr_turn_stun_ctx_cb(NR_SOCKET s, int how, void *arg) { int r, _status; nr_turn_stun_ctx *ctx = (nr_turn_stun_ctx *)arg; ctx->last_error_code = ctx->stun->error_code; switch (ctx->stun->state) { case NR_STUN_CLIENT_STATE_DONE: /* Save the realm and nonce */ if (ctx->stun->realm && (!ctx->tctx->realm || strcmp(ctx->stun->realm, ctx->tctx->realm))) { RFREE(ctx->tctx->realm); ctx->tctx->realm = r_strdup(ctx->stun->realm); if (!ctx->tctx->realm) ABORT(R_NO_MEMORY); } if (ctx->stun->nonce && (!ctx->tctx->nonce || strcmp(ctx->stun->nonce, ctx->tctx->nonce))) { RFREE(ctx->tctx->nonce); ctx->tctx->nonce = r_strdup(ctx->stun->nonce); if (!ctx->tctx->nonce) ABORT(R_NO_MEMORY); } ctx->retry_ct=0; ctx->success_cb(0, 0, ctx); break; case NR_STUN_CLIENT_STATE_FAILED: /* Special case: if this is an authentication error, we retry once. This allows the 401/438 nonce retry paradigm. After that, we fail */ /* TODO([email protected]): 401 needs a #define */ /* TODO([email protected]): Add alternate-server (Mozilla bug 857688) */ if (ctx->stun->error_code == 401 || ctx->stun->error_code == 438) { if (ctx->retry_ct > 0) { r_log(NR_LOG_TURN, LOG_WARNING, "TURN(%s): Exceeded the number of retries", ctx->tctx->label); ABORT(R_FAILED); } if (!ctx->stun->nonce) { r_log(NR_LOG_TURN, LOG_WARNING, "TURN(%s): 401 but no nonce", ctx->tctx->label); ABORT(R_FAILED); } if (!ctx->stun->realm) { r_log(NR_LOG_TURN, LOG_WARNING, "TURN(%s): 401 but no realm", ctx->tctx->label); ABORT(R_FAILED); } /* Try to retry */ if ((r=nr_turn_stun_set_auth_params(ctx, ctx->stun->realm, ctx->stun->nonce))) ABORT(r); ctx->stun->error_code = 0; /* Reset to avoid inf-looping */ if ((r=nr_turn_stun_ctx_start(ctx))) { r_log(NR_LOG_TURN, LOG_ERR, "TURN(%s): Couldn't start STUN", ctx->tctx->label); ABORT(r); } ctx->retry_ct++; } else { ABORT(R_FAILED); } break; case NR_STUN_CLIENT_STATE_TIMED_OUT: ABORT(R_FAILED); break; case NR_STUN_CLIENT_STATE_CANCELLED: assert(0); /* Shouldn't happen */ return; break; default: assert(0); /* Shouldn't happen */ return; } _status=0; abort: if (_status) { ctx->error_cb(0, 0, ctx); } }