Add support for ICE/STUN/TURN in res_rtp_asterisk and chan_sip.
[asterisk/asterisk.git] / res / pjproject / pjnath / src / pjnath / nat_detect.c
1 /* $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/nat_detect.h>
21 #include <pjnath/errno.h>
22 #include <pj/assert.h>
23 #include <pj/ioqueue.h>
24 #include <pj/log.h>
25 #include <pj/os.h>
26 #include <pj/pool.h>
27 #include <pj/rand.h>
28 #include <pj/string.h>
29 #include <pj/timer.h>
30 #include <pj/compat/socket.h>
31
32
33 static const char *nat_type_names[] =
34 {
35     "Unknown",
36     "ErrUnknown",
37     "Open",
38     "Blocked",
39     "Symmetric UDP",
40     "Full Cone",
41     "Symmetric",
42     "Restricted",
43     "Port Restricted"
44 };
45
46
47 #define CHANGE_IP_FLAG          4
48 #define CHANGE_PORT_FLAG        2
49 #define CHANGE_IP_PORT_FLAG     (CHANGE_IP_FLAG | CHANGE_PORT_FLAG)
50 #define TEST_INTERVAL           50
51
52 enum test_type
53 {
54     ST_TEST_1,
55     ST_TEST_2,
56     ST_TEST_3,
57     ST_TEST_1B,
58     ST_MAX
59 };
60
61 static const char *test_names[] =
62 {
63     "Test I: Binding request",
64     "Test II: Binding request with change address and port request",
65     "Test III: Binding request with change port request",
66     "Test IB: Binding request to alternate address"
67 };
68
69 enum timer_type
70 {
71     TIMER_TEST      = 1,
72     TIMER_DESTROY   = 2
73 };
74
75 typedef struct nat_detect_session
76 {
77     pj_pool_t               *pool;
78     pj_mutex_t              *mutex;
79
80     pj_timer_heap_t         *timer_heap;
81     pj_timer_entry           timer;
82     unsigned                 timer_executed;
83
84     void                    *user_data;
85     pj_stun_nat_detect_cb   *cb;
86     pj_sock_t                sock;
87     pj_sockaddr_in           local_addr;
88     pj_ioqueue_key_t        *key;
89     pj_sockaddr_in           server;
90     pj_sockaddr_in          *cur_server;
91     pj_stun_session         *stun_sess;
92
93     pj_ioqueue_op_key_t      read_op, write_op;
94     pj_uint8_t               rx_pkt[PJ_STUN_MAX_PKT_LEN];
95     pj_ssize_t               rx_pkt_len;
96     pj_sockaddr_in           src_addr;
97     int                      src_addr_len;
98
99     struct result
100     {
101         pj_bool_t       executed;
102         pj_bool_t       complete;
103         pj_status_t     status;
104         pj_sockaddr_in  ma;
105         pj_sockaddr_in  ca;
106         pj_stun_tx_data *tdata;
107     } result[ST_MAX];
108
109 } nat_detect_session;
110
111
112 static void on_read_complete(pj_ioqueue_key_t *key, 
113                              pj_ioqueue_op_key_t *op_key, 
114                              pj_ssize_t bytes_read);
115 static void on_request_complete(pj_stun_session *sess,
116                                 pj_status_t status,
117                                 void *token,
118                                 pj_stun_tx_data *tdata,
119                                 const pj_stun_msg *response,
120                                 const pj_sockaddr_t *src_addr,
121                                 unsigned src_addr_len);
122 static pj_status_t on_send_msg(pj_stun_session *sess,
123                                void *token,
124                                const void *pkt,
125                                pj_size_t pkt_size,
126                                const pj_sockaddr_t *dst_addr,
127                                unsigned addr_len);
128
129 static pj_status_t send_test(nat_detect_session *sess,
130                              enum test_type test_id,
131                              const pj_sockaddr_in *alt_addr,
132                              pj_uint32_t change_flag);
133 static void on_sess_timer(pj_timer_heap_t *th,
134                              pj_timer_entry *te);
135 static void sess_destroy(nat_detect_session *sess);
136
137
138 /*
139  * Get the NAT name from the specified NAT type.
140  */
141 PJ_DEF(const char*) pj_stun_get_nat_name(pj_stun_nat_type type)
142 {
143     PJ_ASSERT_RETURN(type >= 0 && type <= PJ_STUN_NAT_TYPE_PORT_RESTRICTED,
144                      "*Invalid*");
145
146     return nat_type_names[type];
147 }
148
149 static int test_executed(nat_detect_session *sess)
150 {
151     unsigned i, count;
152     for (i=0, count=0; i<PJ_ARRAY_SIZE(sess->result); ++i) {
153         if (sess->result[i].executed)
154             ++count;
155     }
156     return count;
157 }
158
159 static int test_completed(nat_detect_session *sess)
160 {
161     unsigned i, count;
162     for (i=0, count=0; i<PJ_ARRAY_SIZE(sess->result); ++i) {
163         if (sess->result[i].complete)
164             ++count;
165     }
166     return count;
167 }
168
169 static pj_status_t get_local_interface(const pj_sockaddr_in *server,
170                                        pj_in_addr *local_addr)
171 {
172     pj_sock_t sock;
173     pj_sockaddr_in tmp;
174     int addr_len;
175     pj_status_t status;
176
177     status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock);
178     if (status != PJ_SUCCESS)
179         return status;
180
181     status = pj_sock_bind_in(sock, 0, 0);
182     if (status != PJ_SUCCESS) {
183         pj_sock_close(sock);
184         return status;
185     }
186
187     status = pj_sock_connect(sock, server, sizeof(pj_sockaddr_in));
188     if (status != PJ_SUCCESS) {
189         pj_sock_close(sock);
190         return status;
191     }
192
193     addr_len = sizeof(pj_sockaddr_in);
194     status = pj_sock_getsockname(sock, &tmp, &addr_len);
195     if (status != PJ_SUCCESS) {
196         pj_sock_close(sock);
197         return status;
198     }
199
200     local_addr->s_addr = tmp.sin_addr.s_addr;
201     
202     pj_sock_close(sock);
203     return PJ_SUCCESS;
204 }
205
206
207 PJ_DEF(pj_status_t) pj_stun_detect_nat_type(const pj_sockaddr_in *server,
208                                             pj_stun_config *stun_cfg,
209                                             void *user_data,
210                                             pj_stun_nat_detect_cb *cb)
211 {
212     pj_pool_t *pool;
213     nat_detect_session *sess;
214     pj_stun_session_cb sess_cb;
215     pj_ioqueue_callback ioqueue_cb;
216     int addr_len;
217     pj_status_t status;
218
219     PJ_ASSERT_RETURN(server && stun_cfg, PJ_EINVAL);
220     PJ_ASSERT_RETURN(stun_cfg->pf && stun_cfg->ioqueue && stun_cfg->timer_heap,
221                      PJ_EINVAL);
222
223     /*
224      * Init NAT detection session.
225      */
226     pool = pj_pool_create(stun_cfg->pf, "natck%p", PJNATH_POOL_LEN_NATCK, 
227                           PJNATH_POOL_INC_NATCK, NULL);
228     if (!pool)
229         return PJ_ENOMEM;
230
231     sess = PJ_POOL_ZALLOC_T(pool, nat_detect_session);
232     sess->pool = pool;
233     sess->user_data = user_data;
234     sess->cb = cb;
235
236     status = pj_mutex_create_recursive(pool, pool->obj_name, &sess->mutex);
237     if (status != PJ_SUCCESS)
238         goto on_error;
239     
240     pj_memcpy(&sess->server, server, sizeof(pj_sockaddr_in));
241
242     /*
243      * Init timer to self-destroy.
244      */
245     sess->timer_heap = stun_cfg->timer_heap;
246     sess->timer.cb = &on_sess_timer;
247     sess->timer.user_data = sess;
248
249
250     /*
251      * Initialize socket.
252      */
253     status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sess->sock);
254     if (status != PJ_SUCCESS)
255         goto on_error;
256
257     /*
258      * Bind to any.
259      */
260     pj_bzero(&sess->local_addr, sizeof(pj_sockaddr_in));
261     sess->local_addr.sin_family = pj_AF_INET();
262     status = pj_sock_bind(sess->sock, &sess->local_addr, 
263                           sizeof(pj_sockaddr_in));
264     if (status != PJ_SUCCESS)
265         goto on_error;
266
267     /*
268      * Get local/bound address.
269      */
270     addr_len = sizeof(sess->local_addr);
271     status = pj_sock_getsockname(sess->sock, &sess->local_addr, &addr_len);
272     if (status != PJ_SUCCESS)
273         goto on_error;
274
275     /*
276      * Find out which interface is used to send to the server.
277      */
278     status = get_local_interface(server, &sess->local_addr.sin_addr);
279     if (status != PJ_SUCCESS)
280         goto on_error;
281
282     PJ_LOG(5,(sess->pool->obj_name, "Local address is %s:%d",
283               pj_inet_ntoa(sess->local_addr.sin_addr), 
284               pj_ntohs(sess->local_addr.sin_port)));
285
286     PJ_LOG(5,(sess->pool->obj_name, "Server set to %s:%d",
287               pj_inet_ntoa(server->sin_addr), 
288               pj_ntohs(server->sin_port)));
289
290     /*
291      * Register socket to ioqueue to receive asynchronous input
292      * notification.
293      */
294     pj_bzero(&ioqueue_cb, sizeof(ioqueue_cb));
295     ioqueue_cb.on_read_complete = &on_read_complete;
296
297     status = pj_ioqueue_register_sock(sess->pool, stun_cfg->ioqueue, 
298                                       sess->sock, sess, &ioqueue_cb,
299                                       &sess->key);
300     if (status != PJ_SUCCESS)
301         goto on_error;
302
303     /*
304      * Create STUN session.
305      */
306     pj_bzero(&sess_cb, sizeof(sess_cb));
307     sess_cb.on_request_complete = &on_request_complete;
308     sess_cb.on_send_msg = &on_send_msg;
309     status = pj_stun_session_create(stun_cfg, pool->obj_name, &sess_cb,
310                                     PJ_FALSE, &sess->stun_sess);
311     if (status != PJ_SUCCESS)
312         goto on_error;
313
314     pj_stun_session_set_user_data(sess->stun_sess, sess);
315
316     /*
317      * Kick-off ioqueue reading.
318      */
319     pj_ioqueue_op_key_init(&sess->read_op, sizeof(sess->read_op));
320     pj_ioqueue_op_key_init(&sess->write_op, sizeof(sess->write_op));
321     on_read_complete(sess->key, &sess->read_op, 0);
322
323     /*
324      * Start TEST_1
325      */
326     sess->timer.id = TIMER_TEST;
327     on_sess_timer(stun_cfg->timer_heap, &sess->timer);
328
329     return PJ_SUCCESS;
330
331 on_error:
332     sess_destroy(sess);
333     return status;
334 }
335
336
337 static void sess_destroy(nat_detect_session *sess)
338 {
339     if (sess->stun_sess) { 
340         pj_stun_session_destroy(sess->stun_sess);
341     }
342
343     if (sess->key) {
344         pj_ioqueue_unregister(sess->key);
345     } else if (sess->sock && sess->sock != PJ_INVALID_SOCKET) {
346         pj_sock_close(sess->sock);
347     }
348
349     if (sess->mutex) {
350         pj_mutex_destroy(sess->mutex);
351     }
352
353     if (sess->pool) {
354         pj_pool_release(sess->pool);
355     }
356 }
357
358
359 static void end_session(nat_detect_session *sess,
360                         pj_status_t status,
361                         pj_stun_nat_type nat_type)
362 {
363     pj_stun_nat_detect_result result;
364     char errmsg[PJ_ERR_MSG_SIZE];
365     pj_time_val delay;
366
367     if (sess->timer.id != 0) {
368         pj_timer_heap_cancel(sess->timer_heap, &sess->timer);
369         sess->timer.id = 0;
370     }
371
372     pj_bzero(&result, sizeof(result));
373     errmsg[0] = '\0';
374     result.status_text = errmsg;
375
376     result.status = status;
377     pj_strerror(status, errmsg, sizeof(errmsg));
378     result.nat_type = nat_type;
379     result.nat_type_name = nat_type_names[result.nat_type];
380
381     if (sess->cb)
382         (*sess->cb)(sess->user_data, &result);
383
384     delay.sec = 0;
385     delay.msec = 0;
386
387     sess->timer.id = TIMER_DESTROY;
388     pj_timer_heap_schedule(sess->timer_heap, &sess->timer, &delay);
389 }
390
391
392 /*
393  * Callback upon receiving packet from network.
394  */
395 static void on_read_complete(pj_ioqueue_key_t *key, 
396                              pj_ioqueue_op_key_t *op_key, 
397                              pj_ssize_t bytes_read)
398 {
399     nat_detect_session *sess;
400     pj_status_t status;
401
402     sess = (nat_detect_session *) pj_ioqueue_get_user_data(key);
403     pj_assert(sess != NULL);
404
405     pj_mutex_lock(sess->mutex);
406
407     if (bytes_read < 0) {
408         if (-bytes_read != PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK) &&
409             -bytes_read != PJ_STATUS_FROM_OS(OSERR_EINPROGRESS) && 
410             -bytes_read != PJ_STATUS_FROM_OS(OSERR_ECONNRESET)) 
411         {
412             /* Permanent error */
413             end_session(sess, -bytes_read, PJ_STUN_NAT_TYPE_ERR_UNKNOWN);
414             goto on_return;
415         }
416
417     } else if (bytes_read > 0) {
418         pj_stun_session_on_rx_pkt(sess->stun_sess, sess->rx_pkt, bytes_read,
419                                   PJ_STUN_IS_DATAGRAM|PJ_STUN_CHECK_PACKET, 
420                                   NULL, NULL, 
421                                   &sess->src_addr, sess->src_addr_len);
422     }
423
424
425     sess->rx_pkt_len = sizeof(sess->rx_pkt);
426     sess->src_addr_len = sizeof(sess->src_addr);
427     status = pj_ioqueue_recvfrom(key, op_key, sess->rx_pkt, &sess->rx_pkt_len,
428                                  PJ_IOQUEUE_ALWAYS_ASYNC, 
429                                  &sess->src_addr, &sess->src_addr_len);
430
431     if (status != PJ_EPENDING) {
432         pj_assert(status != PJ_SUCCESS);
433         end_session(sess, status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN);
434     }
435
436 on_return:
437     pj_mutex_unlock(sess->mutex);
438 }
439
440
441 /*
442  * Callback to send outgoing packet from STUN session.
443  */
444 static pj_status_t on_send_msg(pj_stun_session *stun_sess,
445                                void *token,
446                                const void *pkt,
447                                pj_size_t pkt_size,
448                                const pj_sockaddr_t *dst_addr,
449                                unsigned addr_len)
450 {
451     nat_detect_session *sess;
452     pj_ssize_t pkt_len;
453     pj_status_t status;
454
455     PJ_UNUSED_ARG(token);
456
457     sess = (nat_detect_session*) pj_stun_session_get_user_data(stun_sess);
458
459     pkt_len = pkt_size;
460     status = pj_ioqueue_sendto(sess->key, &sess->write_op, pkt, &pkt_len, 0,
461                                dst_addr, addr_len);
462
463     return status;
464
465 }
466
467 /*
468  * Callback upon request completion.
469  */
470 static void on_request_complete(pj_stun_session *stun_sess,
471                                 pj_status_t status,
472                                 void *token,
473                                 pj_stun_tx_data *tdata,
474                                 const pj_stun_msg *response,
475                                 const pj_sockaddr_t *src_addr,
476                                 unsigned src_addr_len)
477 {
478     nat_detect_session *sess;
479     pj_stun_sockaddr_attr *mattr = NULL;
480     pj_stun_changed_addr_attr *ca = NULL;
481     pj_uint32_t *tsx_id;
482     int cmp;
483     unsigned test_id;
484
485     PJ_UNUSED_ARG(token);
486     PJ_UNUSED_ARG(tdata);
487     PJ_UNUSED_ARG(src_addr);
488     PJ_UNUSED_ARG(src_addr_len);
489
490     sess = (nat_detect_session*) pj_stun_session_get_user_data(stun_sess);
491
492     pj_mutex_lock(sess->mutex);
493
494     /* Find errors in the response */
495     if (status == PJ_SUCCESS) {
496
497         /* Check error message */
498         if (PJ_STUN_IS_ERROR_RESPONSE(response->hdr.type)) {
499             pj_stun_errcode_attr *eattr;
500             int err_code;
501
502             eattr = (pj_stun_errcode_attr*)
503                     pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0);
504
505             if (eattr != NULL)
506                 err_code = eattr->err_code;
507             else
508                 err_code = PJ_STUN_SC_SERVER_ERROR;
509
510             status = PJ_STATUS_FROM_STUN_CODE(err_code);
511
512
513         } else {
514
515             /* Get MAPPED-ADDRESS or XOR-MAPPED-ADDRESS */
516             mattr = (pj_stun_sockaddr_attr*)
517                     pj_stun_msg_find_attr(response, PJ_STUN_ATTR_XOR_MAPPED_ADDR, 0);
518             if (mattr == NULL) {
519                 mattr = (pj_stun_sockaddr_attr*)
520                         pj_stun_msg_find_attr(response, PJ_STUN_ATTR_MAPPED_ADDR, 0);
521             }
522
523             if (mattr == NULL) {
524                 status = PJNATH_ESTUNNOMAPPEDADDR;
525             }
526
527             /* Get CHANGED-ADDRESS attribute */
528             ca = (pj_stun_changed_addr_attr*)
529                  pj_stun_msg_find_attr(response, PJ_STUN_ATTR_CHANGED_ADDR, 0);
530
531             if (ca == NULL) {
532                 status = PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_SERVER_ERROR);
533             }
534
535         }
536     }
537
538     /* Save the result */
539     tsx_id = (pj_uint32_t*) tdata->msg->hdr.tsx_id;
540     test_id = tsx_id[2];
541
542     if (test_id >= ST_MAX) {
543         PJ_LOG(4,(sess->pool->obj_name, "Invalid transaction ID %u in response",
544                   test_id));
545         end_session(sess, PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_SERVER_ERROR),
546                     PJ_STUN_NAT_TYPE_ERR_UNKNOWN);
547         goto on_return;
548     }
549
550     PJ_LOG(5,(sess->pool->obj_name, "Completed %s, status=%d",
551               test_names[test_id], status));
552
553     sess->result[test_id].complete = PJ_TRUE;
554     sess->result[test_id].status = status;
555     if (status == PJ_SUCCESS) {
556         pj_memcpy(&sess->result[test_id].ma, &mattr->sockaddr.ipv4,
557                   sizeof(pj_sockaddr_in));
558         pj_memcpy(&sess->result[test_id].ca, &ca->sockaddr.ipv4,
559                   sizeof(pj_sockaddr_in));
560     }
561
562     /* Send Test 1B only when Test 2 completes. Must not send Test 1B
563      * before Test 2 completes to avoid creating mapping on the NAT.
564      */
565     if (!sess->result[ST_TEST_1B].executed && 
566         sess->result[ST_TEST_2].complete &&
567         sess->result[ST_TEST_2].status != PJ_SUCCESS &&
568         sess->result[ST_TEST_1].complete &&
569         sess->result[ST_TEST_1].status == PJ_SUCCESS) 
570     {
571         cmp = pj_memcmp(&sess->local_addr, &sess->result[ST_TEST_1].ma,
572                         sizeof(pj_sockaddr_in));
573         if (cmp != 0)
574             send_test(sess, ST_TEST_1B, &sess->result[ST_TEST_1].ca, 0);
575     }
576
577     if (test_completed(sess)<3 || test_completed(sess)!=test_executed(sess))
578         goto on_return;
579
580     /* Handle the test result according to RFC 3489 page 22:
581
582
583                         +--------+
584                         |  Test  |
585                         |   1    |
586                         +--------+
587                              |
588                              |
589                              V
590                             /\              /\
591                          N /  \ Y          /  \ Y             +--------+
592           UDP     <-------/Resp\--------->/ IP \------------->|  Test  |
593           Blocked         \ ?  /          \Same/              |   2    |
594                            \  /            \? /               +--------+
595                             \/              \/                    |
596                                              | N                  |
597                                              |                    V
598                                              V                    /\
599                                          +--------+  Sym.      N /  \
600                                          |  Test  |  UDP    <---/Resp\
601                                          |   2    |  Firewall   \ ?  /
602                                          +--------+              \  /
603                                              |                    \/
604                                              V                     |Y
605                   /\                         /\                    |
606    Symmetric  N  /  \       +--------+   N  /  \                   V
607       NAT  <--- / IP \<-----|  Test  |<--- /Resp\               Open
608                 \Same/      |   1B   |     \ ?  /               Internet
609                  \? /       +--------+      \  /
610                   \/                         \/
611                   |                           |Y
612                   |                           |
613                   |                           V
614                   |                           Full
615                   |                           Cone
616                   V              /\
617               +--------+        /  \ Y
618               |  Test  |------>/Resp\---->Restricted
619               |   3    |       \ ?  /
620               +--------+        \  /
621                                  \/
622                                   |N
623                                   |       Port
624                                   +------>Restricted
625
626                  Figure 2: Flow for type discovery process
627      */
628
629     switch (sess->result[ST_TEST_1].status) {
630     case PJNATH_ESTUNTIMEDOUT:
631         /*
632          * Test 1 has timed-out. Conclude with NAT_TYPE_BLOCKED. 
633          */
634         end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_BLOCKED);
635         break;
636     case PJ_SUCCESS:
637         /*
638          * Test 1 is successful. Further tests are needed to detect
639          * NAT type. Compare the MAPPED-ADDRESS with the local address.
640          */
641         cmp = pj_memcmp(&sess->local_addr, &sess->result[ST_TEST_1].ma,
642                         sizeof(pj_sockaddr_in));
643         if (cmp==0) {
644             /*
645              * MAPPED-ADDRESS and local address is equal. Need one more
646              * test to determine NAT type.
647              */
648             switch (sess->result[ST_TEST_2].status) {
649             case PJ_SUCCESS:
650                 /*
651                  * Test 2 is also successful. We're in the open.
652                  */
653                 end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_OPEN);
654                 break;
655             case PJNATH_ESTUNTIMEDOUT:
656                 /*
657                  * Test 2 has timed out. We're behind somekind of UDP
658                  * firewall.
659                  */
660                 end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_SYMMETRIC_UDP);
661                 break;
662             default:
663                 /*
664                  * We've got other error with Test 2.
665                  */
666                 end_session(sess, sess->result[ST_TEST_2].status, 
667                             PJ_STUN_NAT_TYPE_ERR_UNKNOWN);
668                 break;
669             }
670         } else {
671             /*
672              * MAPPED-ADDRESS is different than local address.
673              * We're behind NAT.
674              */
675             switch (sess->result[ST_TEST_2].status) {
676             case PJ_SUCCESS:
677                 /*
678                  * Test 2 is successful. We're behind a full-cone NAT.
679                  */
680                 end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_FULL_CONE);
681                 break;
682             case PJNATH_ESTUNTIMEDOUT:
683                 /*
684                  * Test 2 has timed-out Check result of test 1B..
685                  */
686                 switch (sess->result[ST_TEST_1B].status) {
687                 case PJ_SUCCESS:
688                     /*
689                      * Compare the MAPPED-ADDRESS of test 1B with the
690                      * MAPPED-ADDRESS returned in test 1..
691                      */
692                     cmp = pj_memcmp(&sess->result[ST_TEST_1].ma,
693                                     &sess->result[ST_TEST_1B].ma,
694                                     sizeof(pj_sockaddr_in));
695                     if (cmp != 0) {
696                         /*
697                          * MAPPED-ADDRESS is different, we're behind a
698                          * symmetric NAT.
699                          */
700                         end_session(sess, PJ_SUCCESS,
701                                     PJ_STUN_NAT_TYPE_SYMMETRIC);
702                     } else {
703                         /*
704                          * MAPPED-ADDRESS is equal. We're behind a restricted
705                          * or port-restricted NAT, depending on the result of
706                          * test 3.
707                          */
708                         switch (sess->result[ST_TEST_3].status) {
709                         case PJ_SUCCESS:
710                             /*
711                              * Test 3 is successful, we're behind a restricted
712                              * NAT.
713                              */
714                             end_session(sess, PJ_SUCCESS,
715                                         PJ_STUN_NAT_TYPE_RESTRICTED);
716                             break;
717                         case PJNATH_ESTUNTIMEDOUT:
718                             /*
719                              * Test 3 failed, we're behind a port restricted
720                              * NAT.
721                              */
722                             end_session(sess, PJ_SUCCESS,
723                                         PJ_STUN_NAT_TYPE_PORT_RESTRICTED);
724                             break;
725                         default:
726                             /*
727                              * Got other error with test 3.
728                              */
729                             end_session(sess, sess->result[ST_TEST_3].status,
730                                         PJ_STUN_NAT_TYPE_ERR_UNKNOWN);
731                             break;
732                         }
733                     }
734                     break;
735                 case PJNATH_ESTUNTIMEDOUT:
736                     /*
737                      * Strangely test 1B has failed. Maybe connectivity was
738                      * lost? Or perhaps port 3489 (the usual port number in
739                      * CHANGED-ADDRESS) is blocked?
740                      */
741                     switch (sess->result[ST_TEST_3].status) {
742                     case PJ_SUCCESS:
743                         /* Although test 1B failed, test 3 was successful.
744                          * It could be that port 3489 is blocked, while the
745                          * NAT itself looks to be a Restricted one.
746                          */
747                         end_session(sess, PJ_SUCCESS, 
748                                     PJ_STUN_NAT_TYPE_RESTRICTED);
749                         break;
750                     default:
751                         /* Can't distinguish between Symmetric and Port
752                          * Restricted, so set the type to Unknown
753                          */
754                         end_session(sess, PJ_SUCCESS, 
755                                     PJ_STUN_NAT_TYPE_ERR_UNKNOWN);
756                         break;
757                     }
758                     break;
759                 default:
760                     /*
761                      * Got other error with test 1B.
762                      */
763                     end_session(sess, sess->result[ST_TEST_1B].status,
764                                 PJ_STUN_NAT_TYPE_ERR_UNKNOWN);
765                     break;
766                 }
767                 break;
768             default:
769                 /*
770                  * We've got other error with Test 2.
771                  */
772                 end_session(sess, sess->result[ST_TEST_2].status, 
773                             PJ_STUN_NAT_TYPE_ERR_UNKNOWN);
774                 break;
775             }
776         }
777         break;
778     default:
779         /*
780          * We've got other error with Test 1.
781          */
782         end_session(sess, sess->result[ST_TEST_1].status, 
783                     PJ_STUN_NAT_TYPE_ERR_UNKNOWN);
784         break;
785     }
786
787 on_return:
788     pj_mutex_unlock(sess->mutex);
789 }
790
791
792 /* Perform test */
793 static pj_status_t send_test(nat_detect_session *sess,
794                              enum test_type test_id,
795                              const pj_sockaddr_in *alt_addr,
796                              pj_uint32_t change_flag)
797 {
798     pj_uint32_t magic, tsx_id[3];
799     pj_status_t status;
800
801     sess->result[test_id].executed = PJ_TRUE;
802
803     /* Randomize tsx id */
804     do {
805         magic = pj_rand();
806     } while (magic == PJ_STUN_MAGIC);
807
808     tsx_id[0] = pj_rand();
809     tsx_id[1] = pj_rand();
810     tsx_id[2] = test_id;
811
812     /* Create BIND request */
813     status = pj_stun_session_create_req(sess->stun_sess, 
814                                         PJ_STUN_BINDING_REQUEST, magic,
815                                         (pj_uint8_t*)tsx_id, 
816                                         &sess->result[test_id].tdata);
817     if (status != PJ_SUCCESS)
818         goto on_error;
819
820     /* Add CHANGE-REQUEST attribute */
821     status = pj_stun_msg_add_uint_attr(sess->pool, 
822                                        sess->result[test_id].tdata->msg,
823                                        PJ_STUN_ATTR_CHANGE_REQUEST,
824                                        change_flag);
825     if (status != PJ_SUCCESS)
826         goto on_error;
827
828     /* Configure alternate address */
829     if (alt_addr)
830         sess->cur_server = (pj_sockaddr_in*) alt_addr;
831     else
832         sess->cur_server = &sess->server;
833
834     PJ_LOG(5,(sess->pool->obj_name, 
835               "Performing %s to %s:%d", 
836               test_names[test_id],
837               pj_inet_ntoa(sess->cur_server->sin_addr),
838               pj_ntohs(sess->cur_server->sin_port)));
839
840     /* Send the request */
841     status = pj_stun_session_send_msg(sess->stun_sess, NULL, PJ_TRUE,
842                                       PJ_TRUE, sess->cur_server, 
843                                       sizeof(pj_sockaddr_in),
844                                       sess->result[test_id].tdata);
845     if (status != PJ_SUCCESS)
846         goto on_error;
847
848     return PJ_SUCCESS;
849
850 on_error:
851     sess->result[test_id].complete = PJ_TRUE;
852     sess->result[test_id].status = status;
853
854     return status;
855 }
856
857
858 /* Timer callback */
859 static void on_sess_timer(pj_timer_heap_t *th,
860                              pj_timer_entry *te)
861 {
862     nat_detect_session *sess;
863
864     sess = (nat_detect_session*) te->user_data;
865
866     if (te->id == TIMER_DESTROY) {
867         pj_mutex_lock(sess->mutex);
868         pj_ioqueue_unregister(sess->key);
869         sess->key = NULL;
870         sess->sock = PJ_INVALID_SOCKET;
871         te->id = 0;
872         pj_mutex_unlock(sess->mutex);
873
874         sess_destroy(sess);
875
876     } else if (te->id == TIMER_TEST) {
877
878         pj_bool_t next_timer;
879
880         pj_mutex_lock(sess->mutex);
881
882         next_timer = PJ_FALSE;
883
884         if (sess->timer_executed == 0) {
885             send_test(sess, ST_TEST_1, NULL, 0);
886             next_timer = PJ_TRUE;
887         } else if (sess->timer_executed == 1) {
888             send_test(sess, ST_TEST_2, NULL, CHANGE_IP_PORT_FLAG);
889             next_timer = PJ_TRUE;
890         } else if (sess->timer_executed == 2) {
891             send_test(sess, ST_TEST_3, NULL, CHANGE_PORT_FLAG);
892         } else {
893             pj_assert(!"Shouldn't have timer at this state");
894         }
895
896         ++sess->timer_executed;
897
898         if (next_timer) {
899             pj_time_val delay = {0, TEST_INTERVAL};
900             pj_timer_heap_schedule(th, te, &delay);
901         } else {
902             te->id = 0;
903         }
904
905         pj_mutex_unlock(sess->mutex);
906
907     } else {
908         pj_assert(!"Invalid timer ID");
909     }
910 }
911