blob: 33fccea4676f211fbc71df6495ba10e2a1b529c5 [file] [log] [blame]
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001/* $Id$ */
2/*
3 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
5 *
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.
10 *
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.
15 *
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
19 */
20#include <pjnath/stun_transaction.h>
21#include <pjnath/errno.h>
22#include <pj/assert.h>
23#include <pj/log.h>
24#include <pj/pool.h>
25#include <pj/string.h>
26#include <pj/timer.h>
27
28
29#define THIS_FILE "stun_transaction.c"
30#define TIMER_INACTIVE 0
31#define TIMER_ACTIVE 1
32
33
34struct pj_stun_client_tsx
35{
36 char obj_name[PJ_MAX_OBJ_NAME];
37 pj_stun_tsx_cb cb;
38 void *user_data;
39 pj_grp_lock_t *grp_lock;
40
41 pj_bool_t complete;
42
43 pj_bool_t require_retransmit;
44 unsigned rto_msec;
45 pj_timer_entry retransmit_timer;
46 unsigned transmit_count;
47 pj_time_val retransmit_time;
48 pj_timer_heap_t *timer_heap;
49
50 pj_timer_entry destroy_timer;
51
52 void *last_pkt;
53 unsigned last_pkt_size;
54};
55
56
57#if 1
58# define TRACE_(expr) PJ_LOG(5,expr)
59#else
60# define TRACE_(expr)
61#endif
62
63
64static void retransmit_timer_callback(pj_timer_heap_t *timer_heap,
65 pj_timer_entry *timer);
66static void destroy_timer_callback(pj_timer_heap_t *timer_heap,
67 pj_timer_entry *timer);
68
69/*
70 * Create a STUN client transaction.
71 */
72PJ_DEF(pj_status_t) pj_stun_client_tsx_create(pj_stun_config *cfg,
73 pj_pool_t *pool,
74 pj_grp_lock_t *grp_lock,
75 const pj_stun_tsx_cb *cb,
76 pj_stun_client_tsx **p_tsx)
77{
78 pj_stun_client_tsx *tsx;
79
80 PJ_ASSERT_RETURN(cfg && cb && p_tsx, PJ_EINVAL);
81 PJ_ASSERT_RETURN(cb->on_send_msg, PJ_EINVAL);
82
83 tsx = PJ_POOL_ZALLOC_T(pool, pj_stun_client_tsx);
84 tsx->rto_msec = cfg->rto_msec;
85 tsx->timer_heap = cfg->timer_heap;
86 tsx->grp_lock = grp_lock;
87 pj_memcpy(&tsx->cb, cb, sizeof(*cb));
88
89 tsx->retransmit_timer.cb = &retransmit_timer_callback;
90 tsx->retransmit_timer.user_data = tsx;
91
92 tsx->destroy_timer.cb = &destroy_timer_callback;
93 tsx->destroy_timer.user_data = tsx;
94
95 pj_ansi_snprintf(tsx->obj_name, sizeof(tsx->obj_name), "utsx%p", tsx);
96
97 *p_tsx = tsx;
98
99 PJ_LOG(5,(tsx->obj_name, "STUN client transaction created"));
100 return PJ_SUCCESS;
101}
102
103
104PJ_DEF(pj_status_t) pj_stun_client_tsx_schedule_destroy(
105 pj_stun_client_tsx *tsx,
106 const pj_time_val *delay)
107{
108 pj_status_t status;
109
110 PJ_ASSERT_RETURN(tsx && delay, PJ_EINVAL);
111 PJ_ASSERT_RETURN(tsx->cb.on_destroy, PJ_EINVAL);
112
113 pj_grp_lock_acquire(tsx->grp_lock);
114
115 /* Cancel previously registered timer */
116 pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->destroy_timer,
117 TIMER_INACTIVE);
118
119 /* Stop retransmission, just in case */
120 pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->retransmit_timer,
121 TIMER_INACTIVE);
122
123 status = pj_timer_heap_schedule_w_grp_lock(tsx->timer_heap,
124 &tsx->destroy_timer, delay,
125 TIMER_ACTIVE, tsx->grp_lock);
126 if (status != PJ_SUCCESS) {
127 pj_grp_lock_release(tsx->grp_lock);
128 return status;
129 }
130
131 tsx->cb.on_complete = NULL;
132
133 pj_grp_lock_release(tsx->grp_lock);
134
135 TRACE_((tsx->obj_name, "STUN transaction %p schedule destroy", tsx));
136
137 return PJ_SUCCESS;
138}
139
140
141/*
142 * Destroy transaction immediately.
143 */
144PJ_DEF(pj_status_t) pj_stun_client_tsx_stop(pj_stun_client_tsx *tsx)
145{
146 PJ_ASSERT_RETURN(tsx, PJ_EINVAL);
147
148 /* Don't call grp_lock_acquire() because we might be called on
149 * group lock's destructor.
150 */
151 pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->retransmit_timer,
152 TIMER_INACTIVE);
153 pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->destroy_timer,
154 TIMER_INACTIVE);
155
156 PJ_LOG(5,(tsx->obj_name, "STUN client transaction %p stopped, ref_cnt=%d",
157 tsx, pj_grp_lock_get_ref(tsx->grp_lock)));
158
159 return PJ_SUCCESS;
160}
161
162
163/*
164 * Check if transaction has completed.
165 */
166PJ_DEF(pj_bool_t) pj_stun_client_tsx_is_complete(pj_stun_client_tsx *tsx)
167{
168 PJ_ASSERT_RETURN(tsx, PJ_FALSE);
169 return tsx->complete;
170}
171
172
173/*
174 * Set user data.
175 */
176PJ_DEF(pj_status_t) pj_stun_client_tsx_set_data(pj_stun_client_tsx *tsx,
177 void *data)
178{
179 PJ_ASSERT_RETURN(tsx, PJ_EINVAL);
180 tsx->user_data = data;
181 return PJ_SUCCESS;
182}
183
184
185/*
186 * Get the user data
187 */
188PJ_DEF(void*) pj_stun_client_tsx_get_data(pj_stun_client_tsx *tsx)
189{
190 PJ_ASSERT_RETURN(tsx, NULL);
191 return tsx->user_data;
192}
193
194
195/*
196 * Transmit message.
197 */
198static pj_status_t tsx_transmit_msg(pj_stun_client_tsx *tsx,
199 pj_bool_t mod_count)
200{
201 pj_status_t status;
202
203 PJ_ASSERT_RETURN(tsx->retransmit_timer.id == TIMER_INACTIVE ||
204 !tsx->require_retransmit, PJ_EBUSY);
205
206 if (tsx->require_retransmit && mod_count) {
207 /* Calculate retransmit/timeout delay */
208 if (tsx->transmit_count == 0) {
209 tsx->retransmit_time.sec = 0;
210 tsx->retransmit_time.msec = tsx->rto_msec;
211
212 } else if (tsx->transmit_count < PJ_STUN_MAX_TRANSMIT_COUNT-1) {
213 unsigned msec;
214
215 msec = PJ_TIME_VAL_MSEC(tsx->retransmit_time);
216 msec <<= 1;
217 tsx->retransmit_time.sec = msec / 1000;
218 tsx->retransmit_time.msec = msec % 1000;
219
220 } else {
221 tsx->retransmit_time.sec = PJ_STUN_TIMEOUT_VALUE / 1000;
222 tsx->retransmit_time.msec = PJ_STUN_TIMEOUT_VALUE % 1000;
223 }
224
225 /* Schedule timer first because when send_msg() failed we can
226 * cancel it (as opposed to when schedule_timer() failed we cannot
227 * cancel transmission).
228 */;
229 status = pj_timer_heap_schedule_w_grp_lock(tsx->timer_heap,
230 &tsx->retransmit_timer,
231 &tsx->retransmit_time,
232 TIMER_ACTIVE,
233 tsx->grp_lock);
234 if (status != PJ_SUCCESS) {
235 tsx->retransmit_timer.id = TIMER_INACTIVE;
236 return status;
237 }
238 }
239
240
241 if (mod_count)
242 tsx->transmit_count++;
243
244 PJ_LOG(5,(tsx->obj_name, "STUN sending message (transmit count=%d)",
245 tsx->transmit_count));
246 pj_log_push_indent();
247
248 /* Send message */
249 status = tsx->cb.on_send_msg(tsx, tsx->last_pkt, tsx->last_pkt_size);
250
251 if (status == PJNATH_ESTUNDESTROYED) {
252 /* We've been destroyed, don't access the object. */
253 } else if (status != PJ_SUCCESS) {
254 if (mod_count) {
255 pj_timer_heap_cancel_if_active( tsx->timer_heap,
256 &tsx->retransmit_timer,
257 TIMER_INACTIVE);
258 }
259 PJ_PERROR(4, (tsx->obj_name, status, "STUN error sending message"));
260 }
261
262 pj_log_pop_indent();
263 return status;
264}
265
266
267/*
268 * Send outgoing message and start STUN transaction.
269 */
270PJ_DEF(pj_status_t) pj_stun_client_tsx_send_msg(pj_stun_client_tsx *tsx,
271 pj_bool_t retransmit,
272 void *pkt,
273 unsigned pkt_len)
274{
275 pj_status_t status;
276
277 PJ_ASSERT_RETURN(tsx && pkt && pkt_len, PJ_EINVAL);
278 PJ_ASSERT_RETURN(tsx->retransmit_timer.id == 0, PJ_EBUSY);
279
280 pj_grp_lock_acquire(tsx->grp_lock);
281
282 /* Encode message */
283 tsx->last_pkt = pkt;
284 tsx->last_pkt_size = pkt_len;
285
286 /* Update STUN retransmit flag */
287 tsx->require_retransmit = retransmit;
288
289 /* For TCP, schedule timeout timer after PJ_STUN_TIMEOUT_VALUE.
290 * Since we don't have timeout timer, simulate this by using
291 * retransmit timer.
292 */
293 if (!retransmit) {
294 unsigned timeout;
295
296 pj_assert(tsx->retransmit_timer.id == 0);
297 tsx->transmit_count = PJ_STUN_MAX_TRANSMIT_COUNT;
298
299 timeout = tsx->rto_msec * 16;
300 tsx->retransmit_time.sec = timeout / 1000;
301 tsx->retransmit_time.msec = timeout % 1000;
302
303 /* Schedule timer first because when send_msg() failed we can
304 * cancel it (as opposed to when schedule_timer() failed we cannot
305 * cancel transmission).
306 */;
307 status = pj_timer_heap_schedule_w_grp_lock(tsx->timer_heap,
308 &tsx->retransmit_timer,
309 &tsx->retransmit_time,
310 TIMER_ACTIVE,
311 tsx->grp_lock);
312 if (status != PJ_SUCCESS) {
313 tsx->retransmit_timer.id = TIMER_INACTIVE;
314 pj_grp_lock_release(tsx->grp_lock);
315 return status;
316 }
317 }
318
319 /* Send the message */
320 status = tsx_transmit_msg(tsx, PJ_TRUE);
321 if (status != PJ_SUCCESS) {
322 pj_timer_heap_cancel_if_active(tsx->timer_heap,
323 &tsx->retransmit_timer,
324 TIMER_INACTIVE);
325 pj_grp_lock_release(tsx->grp_lock);
326 return status;
327 }
328
329 pj_grp_lock_release(tsx->grp_lock);
330 return PJ_SUCCESS;
331}
332
333
334/* Retransmit timer callback */
335static void retransmit_timer_callback(pj_timer_heap_t *timer_heap,
336 pj_timer_entry *timer)
337{
338 pj_stun_client_tsx *tsx = (pj_stun_client_tsx *) timer->user_data;
339 pj_status_t status;
340
341 PJ_UNUSED_ARG(timer_heap);
342 pj_grp_lock_acquire(tsx->grp_lock);
343
344 if (tsx->transmit_count >= PJ_STUN_MAX_TRANSMIT_COUNT) {
345 /* tsx may be destroyed when calling the callback below */
346 pj_grp_lock_t *grp_lock = tsx->grp_lock;
347
348 /* Retransmission count exceeded. Transaction has failed */
349 tsx->retransmit_timer.id = 0;
350 PJ_LOG(4,(tsx->obj_name, "STUN timeout waiting for response"));
351 pj_log_push_indent();
352 if (!tsx->complete) {
353 tsx->complete = PJ_TRUE;
354 if (tsx->cb.on_complete) {
355 tsx->cb.on_complete(tsx, PJNATH_ESTUNTIMEDOUT, NULL, NULL, 0);
356 }
357 }
358 pj_grp_lock_release(grp_lock);
359 /* We might have been destroyed, don't try to access the object */
360 pj_log_pop_indent();
361 return;
362 }
363
364 tsx->retransmit_timer.id = 0;
365 status = tsx_transmit_msg(tsx, PJ_TRUE);
366 if (status != PJ_SUCCESS) {
367 tsx->retransmit_timer.id = 0;
368 if (!tsx->complete) {
369 tsx->complete = PJ_TRUE;
370 if (tsx->cb.on_complete) {
371 tsx->cb.on_complete(tsx, status, NULL, NULL, 0);
372 }
373 }
374 }
375
376 pj_grp_lock_release(tsx->grp_lock);
377 /* We might have been destroyed, don't try to access the object */
378}
379
380/*
381 * Request to retransmit the request.
382 */
383PJ_DEF(pj_status_t) pj_stun_client_tsx_retransmit(pj_stun_client_tsx *tsx,
384 pj_bool_t mod_count)
385{
386 if (tsx->destroy_timer.id != 0) {
387 return PJ_SUCCESS;
388 }
389
390 pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->retransmit_timer,
391 TIMER_INACTIVE);
392
393 return tsx_transmit_msg(tsx, mod_count);
394}
395
396/* Timer callback to destroy transaction */
397static void destroy_timer_callback(pj_timer_heap_t *timer_heap,
398 pj_timer_entry *timer)
399{
400 pj_stun_client_tsx *tsx = (pj_stun_client_tsx *) timer->user_data;
401
402 PJ_UNUSED_ARG(timer_heap);
403
404 tsx->destroy_timer.id = PJ_FALSE;
405
406 tsx->cb.on_destroy(tsx);
407 /* Don't access transaction after this */
408}
409
410
411/*
412 * Notify the STUN transaction about the arrival of STUN response.
413 */
414PJ_DEF(pj_status_t) pj_stun_client_tsx_on_rx_msg(pj_stun_client_tsx *tsx,
415 const pj_stun_msg *msg,
416 const pj_sockaddr_t *src_addr,
417 unsigned src_addr_len)
418{
419 pj_stun_errcode_attr *err_attr;
420 pj_status_t status;
421
422 /* Must be STUN response message */
423 if (!PJ_STUN_IS_SUCCESS_RESPONSE(msg->hdr.type) &&
424 !PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type))
425 {
426 PJ_LOG(4,(tsx->obj_name,
427 "STUN rx_msg() error: not response message"));
428 return PJNATH_EINSTUNMSGTYPE;
429 }
430
431
432 /* We have a response with matching transaction ID.
433 * We can cancel retransmit timer now.
434 */
435 pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->retransmit_timer,
436 TIMER_INACTIVE);
437
438 /* Find STUN error code attribute */
439 err_attr = (pj_stun_errcode_attr*)
440 pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ERROR_CODE, 0);
441
442 if (err_attr && err_attr->err_code <= 200) {
443 /* draft-ietf-behave-rfc3489bis-05.txt Section 8.3.2:
444 * Any response between 100 and 299 MUST result in the cessation
445 * of request retransmissions, but otherwise is discarded.
446 */
447 PJ_LOG(4,(tsx->obj_name,
448 "STUN rx_msg() error: received provisional %d code (%.*s)",
449 err_attr->err_code,
450 (int)err_attr->reason.slen,
451 err_attr->reason.ptr));
452 return PJ_SUCCESS;
453 }
454
455 if (err_attr == NULL) {
456 status = PJ_SUCCESS;
457 } else {
458 status = PJ_STATUS_FROM_STUN_CODE(err_attr->err_code);
459 }
460
461 /* Call callback */
462 if (!tsx->complete) {
463 tsx->complete = PJ_TRUE;
464 if (tsx->cb.on_complete) {
465 tsx->cb.on_complete(tsx, status, msg, src_addr, src_addr_len);
466 }
467 /* We might have been destroyed, don't try to access the object */
468 }
469
470 return PJ_SUCCESS;
471
472}
473