1
/* $Id: stun_transaction.c 3553 2011-05-05 06:14:19Z nanang $ */
3
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; either version 2 of the License, or
9
* (at your option) any later version.
11
* This program is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this program; if not, write to the Free Software
18
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
#include <pjnath/stun_transaction.h>
21
#include <pjnath/errno.h>
22
#include <pj/assert.h>
25
#include <pj/string.h>
29
#define TIMER_ACTIVE 1
32
struct pj_stun_client_tsx
34
char obj_name[PJ_MAX_OBJ_NAME];
40
pj_bool_t require_retransmit;
42
pj_timer_entry retransmit_timer;
43
unsigned transmit_count;
44
pj_time_val retransmit_time;
45
pj_timer_heap_t *timer_heap;
47
pj_timer_entry destroy_timer;
50
unsigned last_pkt_size;
54
static void retransmit_timer_callback(pj_timer_heap_t *timer_heap,
55
pj_timer_entry *timer);
56
static void destroy_timer_callback(pj_timer_heap_t *timer_heap,
57
pj_timer_entry *timer);
59
#define stun_perror(tsx,msg,rc) pjnath_perror(tsx->obj_name, msg, rc)
62
* Create a STUN client transaction.
64
PJ_DEF(pj_status_t) pj_stun_client_tsx_create(pj_stun_config *cfg,
66
const pj_stun_tsx_cb *cb,
67
pj_stun_client_tsx **p_tsx)
69
pj_stun_client_tsx *tsx;
71
PJ_ASSERT_RETURN(cfg && cb && p_tsx, PJ_EINVAL);
72
PJ_ASSERT_RETURN(cb->on_send_msg, PJ_EINVAL);
74
tsx = PJ_POOL_ZALLOC_T(pool, pj_stun_client_tsx);
75
tsx->rto_msec = cfg->rto_msec;
76
tsx->timer_heap = cfg->timer_heap;
77
pj_memcpy(&tsx->cb, cb, sizeof(*cb));
79
tsx->retransmit_timer.cb = &retransmit_timer_callback;
80
tsx->retransmit_timer.user_data = tsx;
82
tsx->destroy_timer.cb = &destroy_timer_callback;
83
tsx->destroy_timer.user_data = tsx;
85
pj_ansi_snprintf(tsx->obj_name, sizeof(tsx->obj_name), "stuntsx%p", tsx);
89
PJ_LOG(5,(tsx->obj_name, "STUN client transaction created"));
94
PJ_DEF(pj_status_t) pj_stun_client_tsx_schedule_destroy(
95
pj_stun_client_tsx *tsx,
96
const pj_time_val *delay)
100
PJ_ASSERT_RETURN(tsx && delay, PJ_EINVAL);
101
PJ_ASSERT_RETURN(tsx->cb.on_destroy, PJ_EINVAL);
103
/* Cancel previously registered timer */
104
if (tsx->destroy_timer.id != 0) {
105
pj_timer_heap_cancel(tsx->timer_heap, &tsx->destroy_timer);
106
tsx->destroy_timer.id = 0;
109
/* Stop retransmission, just in case */
110
if (tsx->retransmit_timer.id != 0) {
111
pj_timer_heap_cancel(tsx->timer_heap, &tsx->retransmit_timer);
112
tsx->retransmit_timer.id = 0;
115
status = pj_timer_heap_schedule(tsx->timer_heap,
116
&tsx->destroy_timer, delay);
117
if (status != PJ_SUCCESS)
120
tsx->destroy_timer.id = TIMER_ACTIVE;
121
tsx->cb.on_complete = NULL;
128
* Destroy transaction immediately.
130
PJ_DEF(pj_status_t) pj_stun_client_tsx_destroy(pj_stun_client_tsx *tsx)
132
PJ_ASSERT_RETURN(tsx, PJ_EINVAL);
134
if (tsx->retransmit_timer.id != 0) {
135
pj_timer_heap_cancel(tsx->timer_heap, &tsx->retransmit_timer);
136
tsx->retransmit_timer.id = 0;
138
if (tsx->destroy_timer.id != 0) {
139
pj_timer_heap_cancel(tsx->timer_heap, &tsx->destroy_timer);
140
tsx->destroy_timer.id = 0;
143
PJ_LOG(5,(tsx->obj_name, "STUN client transaction destroyed"));
149
* Check if transaction has completed.
151
PJ_DEF(pj_bool_t) pj_stun_client_tsx_is_complete(pj_stun_client_tsx *tsx)
153
PJ_ASSERT_RETURN(tsx, PJ_FALSE);
154
return tsx->complete;
161
PJ_DEF(pj_status_t) pj_stun_client_tsx_set_data(pj_stun_client_tsx *tsx,
164
PJ_ASSERT_RETURN(tsx, PJ_EINVAL);
165
tsx->user_data = data;
173
PJ_DEF(void*) pj_stun_client_tsx_get_data(pj_stun_client_tsx *tsx)
175
PJ_ASSERT_RETURN(tsx, NULL);
176
return tsx->user_data;
183
static pj_status_t tsx_transmit_msg(pj_stun_client_tsx *tsx)
187
PJ_ASSERT_RETURN(tsx->retransmit_timer.id == 0 ||
188
!tsx->require_retransmit, PJ_EBUSY);
190
if (tsx->require_retransmit) {
191
/* Calculate retransmit/timeout delay */
192
if (tsx->transmit_count == 0) {
193
tsx->retransmit_time.sec = 0;
194
tsx->retransmit_time.msec = tsx->rto_msec;
196
} else if (tsx->transmit_count < PJ_STUN_MAX_TRANSMIT_COUNT-1) {
199
msec = PJ_TIME_VAL_MSEC(tsx->retransmit_time);
201
tsx->retransmit_time.sec = msec / 1000;
202
tsx->retransmit_time.msec = msec % 1000;
205
tsx->retransmit_time.sec = PJ_STUN_TIMEOUT_VALUE / 1000;
206
tsx->retransmit_time.msec = PJ_STUN_TIMEOUT_VALUE % 1000;
209
/* Schedule timer first because when send_msg() failed we can
210
* cancel it (as opposed to when schedule_timer() failed we cannot
211
* cancel transmission).
213
status = pj_timer_heap_schedule(tsx->timer_heap,
214
&tsx->retransmit_timer,
215
&tsx->retransmit_time);
216
if (status != PJ_SUCCESS) {
217
tsx->retransmit_timer.id = 0;
220
tsx->retransmit_timer.id = TIMER_ACTIVE;
224
tsx->transmit_count++;
226
PJ_LOG(5,(tsx->obj_name, "STUN sending message (transmit count=%d)",
227
tsx->transmit_count));
230
status = tsx->cb.on_send_msg(tsx, tsx->last_pkt, tsx->last_pkt_size);
232
if (status == PJNATH_ESTUNDESTROYED) {
233
/* We've been destroyed, don't access the object. */
234
} else if (status != PJ_SUCCESS) {
235
if (tsx->retransmit_timer.id != 0) {
236
pj_timer_heap_cancel(tsx->timer_heap,
237
&tsx->retransmit_timer);
238
tsx->retransmit_timer.id = 0;
240
stun_perror(tsx, "STUN error sending message", status);
249
* Send outgoing message and start STUN transaction.
251
PJ_DEF(pj_status_t) pj_stun_client_tsx_send_msg(pj_stun_client_tsx *tsx,
252
pj_bool_t retransmit,
258
PJ_ASSERT_RETURN(tsx && pkt && pkt_len, PJ_EINVAL);
259
PJ_ASSERT_RETURN(tsx->retransmit_timer.id == 0, PJ_EBUSY);
263
tsx->last_pkt_size = pkt_len;
265
/* Update STUN retransmit flag */
266
tsx->require_retransmit = retransmit;
268
/* For TCP, schedule timeout timer after PJ_STUN_TIMEOUT_VALUE.
269
* Since we don't have timeout timer, simulate this by using
275
pj_assert(tsx->retransmit_timer.id == 0);
276
tsx->transmit_count = PJ_STUN_MAX_TRANSMIT_COUNT;
278
timeout = tsx->rto_msec * 16;
279
tsx->retransmit_time.sec = timeout / 1000;
280
tsx->retransmit_time.msec = timeout % 1000;
282
/* Schedule timer first because when send_msg() failed we can
283
* cancel it (as opposed to when schedule_timer() failed we cannot
284
* cancel transmission).
286
status = pj_timer_heap_schedule(tsx->timer_heap,
287
&tsx->retransmit_timer,
288
&tsx->retransmit_time);
289
if (status != PJ_SUCCESS) {
290
tsx->retransmit_timer.id = 0;
293
tsx->retransmit_timer.id = TIMER_ACTIVE;
296
/* Send the message */
297
status = tsx_transmit_msg(tsx);
298
if (status != PJ_SUCCESS) {
299
if (tsx->retransmit_timer.id != 0) {
300
pj_timer_heap_cancel(tsx->timer_heap,
301
&tsx->retransmit_timer);
302
tsx->retransmit_timer.id = 0;
311
/* Retransmit timer callback */
312
static void retransmit_timer_callback(pj_timer_heap_t *timer_heap,
313
pj_timer_entry *timer)
315
pj_stun_client_tsx *tsx = (pj_stun_client_tsx *) timer->user_data;
318
PJ_UNUSED_ARG(timer_heap);
320
if (tsx->transmit_count >= PJ_STUN_MAX_TRANSMIT_COUNT) {
321
/* Retransmission count exceeded. Transaction has failed */
322
tsx->retransmit_timer.id = 0;
323
PJ_LOG(4,(tsx->obj_name, "STUN timeout waiting for response"));
324
if (!tsx->complete) {
325
tsx->complete = PJ_TRUE;
326
if (tsx->cb.on_complete) {
327
tsx->cb.on_complete(tsx, PJNATH_ESTUNTIMEDOUT, NULL, NULL, 0);
330
/* We might have been destroyed, don't try to access the object */
334
tsx->retransmit_timer.id = 0;
335
status = tsx_transmit_msg(tsx);
336
if (status == PJNATH_ESTUNDESTROYED) {
337
/* We've been destroyed, don't try to access the object */
338
} else if (status != PJ_SUCCESS) {
339
tsx->retransmit_timer.id = 0;
340
if (!tsx->complete) {
341
tsx->complete = PJ_TRUE;
342
if (tsx->cb.on_complete) {
343
tsx->cb.on_complete(tsx, status, NULL, NULL, 0);
346
/* We might have been destroyed, don't try to access the object */
351
* Request to retransmit the request.
353
PJ_DEF(pj_status_t) pj_stun_client_tsx_retransmit(pj_stun_client_tsx *tsx)
355
if (tsx->destroy_timer.id != 0) {
359
if (tsx->retransmit_timer.id != 0) {
360
pj_timer_heap_cancel(tsx->timer_heap, &tsx->retransmit_timer);
361
tsx->retransmit_timer.id = 0;
364
return tsx_transmit_msg(tsx);
367
/* Timer callback to destroy transaction */
368
static void destroy_timer_callback(pj_timer_heap_t *timer_heap,
369
pj_timer_entry *timer)
371
pj_stun_client_tsx *tsx = (pj_stun_client_tsx *) timer->user_data;
373
PJ_UNUSED_ARG(timer_heap);
375
tsx->destroy_timer.id = PJ_FALSE;
376
tsx->cb.on_destroy(tsx);
377
/* Don't access transaction after this */
382
* Notify the STUN transaction about the arrival of STUN response.
384
PJ_DEF(pj_status_t) pj_stun_client_tsx_on_rx_msg(pj_stun_client_tsx *tsx,
385
const pj_stun_msg *msg,
386
const pj_sockaddr_t *src_addr,
387
unsigned src_addr_len)
389
pj_stun_errcode_attr *err_attr;
392
/* Must be STUN response message */
393
if (!PJ_STUN_IS_SUCCESS_RESPONSE(msg->hdr.type) &&
394
!PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type))
396
PJ_LOG(4,(tsx->obj_name,
397
"STUN rx_msg() error: not response message"));
398
return PJNATH_EINSTUNMSGTYPE;
402
/* We have a response with matching transaction ID.
403
* We can cancel retransmit timer now.
405
if (tsx->retransmit_timer.id) {
406
pj_timer_heap_cancel(tsx->timer_heap, &tsx->retransmit_timer);
407
tsx->retransmit_timer.id = 0;
410
/* Find STUN error code attribute */
411
err_attr = (pj_stun_errcode_attr*)
412
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ERROR_CODE, 0);
414
if (err_attr && err_attr->err_code <= 200) {
415
/* draft-ietf-behave-rfc3489bis-05.txt Section 8.3.2:
416
* Any response between 100 and 299 MUST result in the cessation
417
* of request retransmissions, but otherwise is discarded.
419
PJ_LOG(4,(tsx->obj_name,
420
"STUN rx_msg() error: received provisional %d code (%.*s)",
422
(int)err_attr->reason.slen,
423
err_attr->reason.ptr));
427
if (err_attr == NULL) {
430
status = PJ_STATUS_FROM_STUN_CODE(err_attr->err_code);
434
if (!tsx->complete) {
435
tsx->complete = PJ_TRUE;
436
if (tsx->cb.on_complete) {
437
tsx->cb.on_complete(tsx, status, msg, src_addr, src_addr_len);
439
/* We might have been destroyed, don't try to access the object */