1
/* $Id: stun_transaction.c 3753 2011-09-18 14:59:56Z bennylp $ */
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));
228
pj_log_push_indent();
231
status = tsx->cb.on_send_msg(tsx, tsx->last_pkt, tsx->last_pkt_size);
233
if (status == PJNATH_ESTUNDESTROYED) {
234
/* We've been destroyed, don't access the object. */
235
} else if (status != PJ_SUCCESS) {
236
if (tsx->retransmit_timer.id != 0) {
237
pj_timer_heap_cancel(tsx->timer_heap,
238
&tsx->retransmit_timer);
239
tsx->retransmit_timer.id = 0;
241
stun_perror(tsx, "STUN error sending message", status);
250
* Send outgoing message and start STUN transaction.
252
PJ_DEF(pj_status_t) pj_stun_client_tsx_send_msg(pj_stun_client_tsx *tsx,
253
pj_bool_t retransmit,
259
PJ_ASSERT_RETURN(tsx && pkt && pkt_len, PJ_EINVAL);
260
PJ_ASSERT_RETURN(tsx->retransmit_timer.id == 0, PJ_EBUSY);
264
tsx->last_pkt_size = pkt_len;
266
/* Update STUN retransmit flag */
267
tsx->require_retransmit = retransmit;
269
/* For TCP, schedule timeout timer after PJ_STUN_TIMEOUT_VALUE.
270
* Since we don't have timeout timer, simulate this by using
276
pj_assert(tsx->retransmit_timer.id == 0);
277
tsx->transmit_count = PJ_STUN_MAX_TRANSMIT_COUNT;
279
timeout = tsx->rto_msec * 16;
280
tsx->retransmit_time.sec = timeout / 1000;
281
tsx->retransmit_time.msec = timeout % 1000;
283
/* Schedule timer first because when send_msg() failed we can
284
* cancel it (as opposed to when schedule_timer() failed we cannot
285
* cancel transmission).
287
status = pj_timer_heap_schedule(tsx->timer_heap,
288
&tsx->retransmit_timer,
289
&tsx->retransmit_time);
290
if (status != PJ_SUCCESS) {
291
tsx->retransmit_timer.id = 0;
294
tsx->retransmit_timer.id = TIMER_ACTIVE;
297
/* Send the message */
298
status = tsx_transmit_msg(tsx);
299
if (status != PJ_SUCCESS) {
300
if (tsx->retransmit_timer.id != 0) {
301
pj_timer_heap_cancel(tsx->timer_heap,
302
&tsx->retransmit_timer);
303
tsx->retransmit_timer.id = 0;
312
/* Retransmit timer callback */
313
static void retransmit_timer_callback(pj_timer_heap_t *timer_heap,
314
pj_timer_entry *timer)
316
pj_stun_client_tsx *tsx = (pj_stun_client_tsx *) timer->user_data;
319
PJ_UNUSED_ARG(timer_heap);
321
if (tsx->transmit_count >= PJ_STUN_MAX_TRANSMIT_COUNT) {
322
/* Retransmission count exceeded. Transaction has failed */
323
tsx->retransmit_timer.id = 0;
324
PJ_LOG(4,(tsx->obj_name, "STUN timeout waiting for response"));
325
pj_log_push_indent();
326
if (!tsx->complete) {
327
tsx->complete = PJ_TRUE;
328
if (tsx->cb.on_complete) {
329
tsx->cb.on_complete(tsx, PJNATH_ESTUNTIMEDOUT, NULL, NULL, 0);
332
/* We might have been destroyed, don't try to access the object */
337
tsx->retransmit_timer.id = 0;
338
status = tsx_transmit_msg(tsx);
339
if (status == PJNATH_ESTUNDESTROYED) {
340
/* We've been destroyed, don't try to access the object */
341
} else if (status != PJ_SUCCESS) {
342
tsx->retransmit_timer.id = 0;
343
if (!tsx->complete) {
344
tsx->complete = PJ_TRUE;
345
if (tsx->cb.on_complete) {
346
tsx->cb.on_complete(tsx, status, NULL, NULL, 0);
349
/* We might have been destroyed, don't try to access the object */
354
* Request to retransmit the request.
356
PJ_DEF(pj_status_t) pj_stun_client_tsx_retransmit(pj_stun_client_tsx *tsx)
358
if (tsx->destroy_timer.id != 0) {
362
if (tsx->retransmit_timer.id != 0) {
363
pj_timer_heap_cancel(tsx->timer_heap, &tsx->retransmit_timer);
364
tsx->retransmit_timer.id = 0;
367
return tsx_transmit_msg(tsx);
370
/* Timer callback to destroy transaction */
371
static void destroy_timer_callback(pj_timer_heap_t *timer_heap,
372
pj_timer_entry *timer)
374
pj_stun_client_tsx *tsx = (pj_stun_client_tsx *) timer->user_data;
376
PJ_UNUSED_ARG(timer_heap);
378
tsx->destroy_timer.id = PJ_FALSE;
379
tsx->cb.on_destroy(tsx);
380
/* Don't access transaction after this */
385
* Notify the STUN transaction about the arrival of STUN response.
387
PJ_DEF(pj_status_t) pj_stun_client_tsx_on_rx_msg(pj_stun_client_tsx *tsx,
388
const pj_stun_msg *msg,
389
const pj_sockaddr_t *src_addr,
390
unsigned src_addr_len)
392
pj_stun_errcode_attr *err_attr;
395
/* Must be STUN response message */
396
if (!PJ_STUN_IS_SUCCESS_RESPONSE(msg->hdr.type) &&
397
!PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type))
399
PJ_LOG(4,(tsx->obj_name,
400
"STUN rx_msg() error: not response message"));
401
return PJNATH_EINSTUNMSGTYPE;
405
/* We have a response with matching transaction ID.
406
* We can cancel retransmit timer now.
408
if (tsx->retransmit_timer.id) {
409
pj_timer_heap_cancel(tsx->timer_heap, &tsx->retransmit_timer);
410
tsx->retransmit_timer.id = 0;
413
/* Find STUN error code attribute */
414
err_attr = (pj_stun_errcode_attr*)
415
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ERROR_CODE, 0);
417
if (err_attr && err_attr->err_code <= 200) {
418
/* draft-ietf-behave-rfc3489bis-05.txt Section 8.3.2:
419
* Any response between 100 and 299 MUST result in the cessation
420
* of request retransmissions, but otherwise is discarded.
422
PJ_LOG(4,(tsx->obj_name,
423
"STUN rx_msg() error: received provisional %d code (%.*s)",
425
(int)err_attr->reason.slen,
426
err_attr->reason.ptr));
430
if (err_attr == NULL) {
433
status = PJ_STATUS_FROM_STUN_CODE(err_attr->err_code);
437
if (!tsx->complete) {
438
tsx->complete = PJ_TRUE;
439
if (tsx->cb.on_complete) {
440
tsx->cb.on_complete(tsx, status, msg, src_addr, src_addr_len);
442
/* We might have been destroyed, don't try to access the object */