Alexandre Lision | 8af73cb | 2013-12-10 14:11:20 -0500 | [diff] [blame] | 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_grp_lock_t *grp_lock; |
| 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 | static void sess_on_destroy(void *member); |
| 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_grp_lock_create(pool, NULL, &sess->grp_lock); |
| 237 | if (status != PJ_SUCCESS) { |
| 238 | /* Group lock not created yet, just destroy pool and return */ |
| 239 | pj_pool_release(pool); |
| 240 | return status; |
| 241 | } |
| 242 | |
| 243 | pj_grp_lock_add_ref(sess->grp_lock); |
| 244 | pj_grp_lock_add_handler(sess->grp_lock, pool, sess, &sess_on_destroy); |
| 245 | |
| 246 | pj_memcpy(&sess->server, server, sizeof(pj_sockaddr_in)); |
| 247 | |
| 248 | /* |
| 249 | * Init timer to self-destroy. |
| 250 | */ |
| 251 | sess->timer_heap = stun_cfg->timer_heap; |
| 252 | sess->timer.cb = &on_sess_timer; |
| 253 | sess->timer.user_data = sess; |
| 254 | |
| 255 | |
| 256 | /* |
| 257 | * Initialize socket. |
| 258 | */ |
| 259 | status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sess->sock); |
| 260 | if (status != PJ_SUCCESS) |
| 261 | goto on_error; |
| 262 | |
| 263 | /* |
| 264 | * Bind to any. |
| 265 | */ |
| 266 | pj_bzero(&sess->local_addr, sizeof(pj_sockaddr_in)); |
| 267 | sess->local_addr.sin_family = pj_AF_INET(); |
| 268 | status = pj_sock_bind(sess->sock, &sess->local_addr, |
| 269 | sizeof(pj_sockaddr_in)); |
| 270 | if (status != PJ_SUCCESS) |
| 271 | goto on_error; |
| 272 | |
| 273 | /* |
| 274 | * Get local/bound address. |
| 275 | */ |
| 276 | addr_len = sizeof(sess->local_addr); |
| 277 | status = pj_sock_getsockname(sess->sock, &sess->local_addr, &addr_len); |
| 278 | if (status != PJ_SUCCESS) |
| 279 | goto on_error; |
| 280 | |
| 281 | /* |
| 282 | * Find out which interface is used to send to the server. |
| 283 | */ |
| 284 | status = get_local_interface(server, &sess->local_addr.sin_addr); |
| 285 | if (status != PJ_SUCCESS) |
| 286 | goto on_error; |
| 287 | |
| 288 | PJ_LOG(5,(sess->pool->obj_name, "Local address is %s:%d", |
| 289 | pj_inet_ntoa(sess->local_addr.sin_addr), |
| 290 | pj_ntohs(sess->local_addr.sin_port))); |
| 291 | |
| 292 | PJ_LOG(5,(sess->pool->obj_name, "Server set to %s:%d", |
| 293 | pj_inet_ntoa(server->sin_addr), |
| 294 | pj_ntohs(server->sin_port))); |
| 295 | |
| 296 | /* |
| 297 | * Register socket to ioqueue to receive asynchronous input |
| 298 | * notification. |
| 299 | */ |
| 300 | pj_bzero(&ioqueue_cb, sizeof(ioqueue_cb)); |
| 301 | ioqueue_cb.on_read_complete = &on_read_complete; |
| 302 | |
| 303 | status = pj_ioqueue_register_sock2(sess->pool, stun_cfg->ioqueue, |
| 304 | sess->sock, sess->grp_lock, sess, |
| 305 | &ioqueue_cb, &sess->key); |
| 306 | if (status != PJ_SUCCESS) |
| 307 | goto on_error; |
| 308 | |
| 309 | /* |
| 310 | * Create STUN session. |
| 311 | */ |
| 312 | pj_bzero(&sess_cb, sizeof(sess_cb)); |
| 313 | sess_cb.on_request_complete = &on_request_complete; |
| 314 | sess_cb.on_send_msg = &on_send_msg; |
| 315 | status = pj_stun_session_create(stun_cfg, pool->obj_name, &sess_cb, |
| 316 | PJ_FALSE, sess->grp_lock, &sess->stun_sess); |
| 317 | if (status != PJ_SUCCESS) |
| 318 | goto on_error; |
| 319 | |
| 320 | pj_stun_session_set_user_data(sess->stun_sess, sess); |
| 321 | |
| 322 | /* |
| 323 | * Kick-off ioqueue reading. |
| 324 | */ |
| 325 | pj_ioqueue_op_key_init(&sess->read_op, sizeof(sess->read_op)); |
| 326 | pj_ioqueue_op_key_init(&sess->write_op, sizeof(sess->write_op)); |
| 327 | on_read_complete(sess->key, &sess->read_op, 0); |
| 328 | |
| 329 | /* |
| 330 | * Start TEST_1 |
| 331 | */ |
| 332 | sess->timer.id = TIMER_TEST; |
| 333 | on_sess_timer(stun_cfg->timer_heap, &sess->timer); |
| 334 | |
| 335 | return PJ_SUCCESS; |
| 336 | |
| 337 | on_error: |
| 338 | sess_destroy(sess); |
| 339 | return status; |
| 340 | } |
| 341 | |
| 342 | |
| 343 | static void sess_destroy(nat_detect_session *sess) |
| 344 | { |
| 345 | if (sess->stun_sess) { |
| 346 | pj_stun_session_destroy(sess->stun_sess); |
| 347 | sess->stun_sess = NULL; |
| 348 | } |
| 349 | |
| 350 | if (sess->key) { |
| 351 | pj_ioqueue_unregister(sess->key); |
| 352 | sess->key = NULL; |
| 353 | sess->sock = PJ_INVALID_SOCKET; |
| 354 | } else if (sess->sock && sess->sock != PJ_INVALID_SOCKET) { |
| 355 | pj_sock_close(sess->sock); |
| 356 | sess->sock = PJ_INVALID_SOCKET; |
| 357 | } |
| 358 | |
| 359 | if (sess->grp_lock) { |
| 360 | pj_grp_lock_dec_ref(sess->grp_lock); |
| 361 | } |
| 362 | } |
| 363 | |
| 364 | static void sess_on_destroy(void *member) |
| 365 | { |
| 366 | nat_detect_session *sess = (nat_detect_session*)member; |
| 367 | if (sess->pool) { |
| 368 | pj_pool_release(sess->pool); |
| 369 | } |
| 370 | } |
| 371 | |
| 372 | static void end_session(nat_detect_session *sess, |
| 373 | pj_status_t status, |
| 374 | pj_stun_nat_type nat_type) |
| 375 | { |
| 376 | pj_stun_nat_detect_result result; |
| 377 | char errmsg[PJ_ERR_MSG_SIZE]; |
| 378 | pj_time_val delay; |
| 379 | |
| 380 | if (sess->timer.id != 0) { |
| 381 | pj_timer_heap_cancel(sess->timer_heap, &sess->timer); |
| 382 | sess->timer.id = 0; |
| 383 | } |
| 384 | |
| 385 | pj_bzero(&result, sizeof(result)); |
| 386 | errmsg[0] = '\0'; |
| 387 | result.status_text = errmsg; |
| 388 | |
| 389 | result.status = status; |
| 390 | pj_strerror(status, errmsg, sizeof(errmsg)); |
| 391 | result.nat_type = nat_type; |
| 392 | result.nat_type_name = nat_type_names[result.nat_type]; |
| 393 | |
| 394 | if (sess->cb) |
| 395 | (*sess->cb)(sess->user_data, &result); |
| 396 | |
| 397 | delay.sec = 0; |
| 398 | delay.msec = 0; |
| 399 | |
| 400 | sess->timer.id = TIMER_DESTROY; |
| 401 | pj_timer_heap_schedule(sess->timer_heap, &sess->timer, &delay); |
| 402 | } |
| 403 | |
| 404 | |
| 405 | /* |
| 406 | * Callback upon receiving packet from network. |
| 407 | */ |
| 408 | static void on_read_complete(pj_ioqueue_key_t *key, |
| 409 | pj_ioqueue_op_key_t *op_key, |
| 410 | pj_ssize_t bytes_read) |
| 411 | { |
| 412 | nat_detect_session *sess; |
| 413 | pj_status_t status; |
| 414 | |
| 415 | sess = (nat_detect_session *) pj_ioqueue_get_user_data(key); |
| 416 | pj_assert(sess != NULL); |
| 417 | |
| 418 | pj_grp_lock_acquire(sess->grp_lock); |
| 419 | |
| 420 | /* Ignore packet when STUN session has been destroyed */ |
| 421 | if (!sess->stun_sess) |
| 422 | goto on_return; |
| 423 | |
| 424 | if (bytes_read < 0) { |
| 425 | if (-bytes_read != PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK) && |
| 426 | -bytes_read != PJ_STATUS_FROM_OS(OSERR_EINPROGRESS) && |
| 427 | -bytes_read != PJ_STATUS_FROM_OS(OSERR_ECONNRESET)) |
| 428 | { |
| 429 | /* Permanent error */ |
| 430 | end_session(sess, (pj_status_t)-bytes_read, |
| 431 | PJ_STUN_NAT_TYPE_ERR_UNKNOWN); |
| 432 | goto on_return; |
| 433 | } |
| 434 | |
| 435 | } else if (bytes_read > 0) { |
| 436 | pj_stun_session_on_rx_pkt(sess->stun_sess, sess->rx_pkt, bytes_read, |
| 437 | PJ_STUN_IS_DATAGRAM|PJ_STUN_CHECK_PACKET, |
| 438 | NULL, NULL, |
| 439 | &sess->src_addr, sess->src_addr_len); |
| 440 | } |
| 441 | |
| 442 | |
| 443 | sess->rx_pkt_len = sizeof(sess->rx_pkt); |
| 444 | sess->src_addr_len = sizeof(sess->src_addr); |
| 445 | status = pj_ioqueue_recvfrom(key, op_key, sess->rx_pkt, &sess->rx_pkt_len, |
| 446 | PJ_IOQUEUE_ALWAYS_ASYNC, |
| 447 | &sess->src_addr, &sess->src_addr_len); |
| 448 | |
| 449 | if (status != PJ_EPENDING) { |
| 450 | pj_assert(status != PJ_SUCCESS); |
| 451 | end_session(sess, status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); |
| 452 | } |
| 453 | |
| 454 | on_return: |
| 455 | pj_grp_lock_release(sess->grp_lock); |
| 456 | } |
| 457 | |
| 458 | |
| 459 | /* |
| 460 | * Callback to send outgoing packet from STUN session. |
| 461 | */ |
| 462 | static pj_status_t on_send_msg(pj_stun_session *stun_sess, |
| 463 | void *token, |
| 464 | const void *pkt, |
| 465 | pj_size_t pkt_size, |
| 466 | const pj_sockaddr_t *dst_addr, |
| 467 | unsigned addr_len) |
| 468 | { |
| 469 | nat_detect_session *sess; |
| 470 | pj_ssize_t pkt_len; |
| 471 | pj_status_t status; |
| 472 | |
| 473 | PJ_UNUSED_ARG(token); |
| 474 | |
| 475 | sess = (nat_detect_session*) pj_stun_session_get_user_data(stun_sess); |
| 476 | |
| 477 | pkt_len = pkt_size; |
| 478 | status = pj_ioqueue_sendto(sess->key, &sess->write_op, pkt, &pkt_len, 0, |
| 479 | dst_addr, addr_len); |
| 480 | |
| 481 | return status; |
| 482 | |
| 483 | } |
| 484 | |
| 485 | /* |
| 486 | * Callback upon request completion. |
| 487 | */ |
| 488 | static void on_request_complete(pj_stun_session *stun_sess, |
| 489 | pj_status_t status, |
| 490 | void *token, |
| 491 | pj_stun_tx_data *tdata, |
| 492 | const pj_stun_msg *response, |
| 493 | const pj_sockaddr_t *src_addr, |
| 494 | unsigned src_addr_len) |
| 495 | { |
| 496 | nat_detect_session *sess; |
| 497 | pj_stun_sockaddr_attr *mattr = NULL; |
| 498 | pj_stun_changed_addr_attr *ca = NULL; |
| 499 | pj_uint32_t *tsx_id; |
| 500 | int cmp; |
| 501 | unsigned test_id; |
| 502 | |
| 503 | PJ_UNUSED_ARG(token); |
| 504 | PJ_UNUSED_ARG(tdata); |
| 505 | PJ_UNUSED_ARG(src_addr); |
| 506 | PJ_UNUSED_ARG(src_addr_len); |
| 507 | |
| 508 | sess = (nat_detect_session*) pj_stun_session_get_user_data(stun_sess); |
| 509 | |
| 510 | pj_grp_lock_acquire(sess->grp_lock); |
| 511 | |
| 512 | /* Find errors in the response */ |
| 513 | if (status == PJ_SUCCESS) { |
| 514 | |
| 515 | /* Check error message */ |
| 516 | if (PJ_STUN_IS_ERROR_RESPONSE(response->hdr.type)) { |
| 517 | pj_stun_errcode_attr *eattr; |
| 518 | int err_code; |
| 519 | |
| 520 | eattr = (pj_stun_errcode_attr*) |
| 521 | pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0); |
| 522 | |
| 523 | if (eattr != NULL) |
| 524 | err_code = eattr->err_code; |
| 525 | else |
| 526 | err_code = PJ_STUN_SC_SERVER_ERROR; |
| 527 | |
| 528 | status = PJ_STATUS_FROM_STUN_CODE(err_code); |
| 529 | |
| 530 | |
| 531 | } else { |
| 532 | |
| 533 | /* Get MAPPED-ADDRESS or XOR-MAPPED-ADDRESS */ |
| 534 | mattr = (pj_stun_sockaddr_attr*) |
| 535 | pj_stun_msg_find_attr(response, PJ_STUN_ATTR_XOR_MAPPED_ADDR, 0); |
| 536 | if (mattr == NULL) { |
| 537 | mattr = (pj_stun_sockaddr_attr*) |
| 538 | pj_stun_msg_find_attr(response, PJ_STUN_ATTR_MAPPED_ADDR, 0); |
| 539 | } |
| 540 | |
| 541 | if (mattr == NULL) { |
| 542 | status = PJNATH_ESTUNNOMAPPEDADDR; |
| 543 | } |
| 544 | |
| 545 | /* Get CHANGED-ADDRESS attribute */ |
| 546 | ca = (pj_stun_changed_addr_attr*) |
| 547 | pj_stun_msg_find_attr(response, PJ_STUN_ATTR_CHANGED_ADDR, 0); |
| 548 | |
| 549 | if (ca == NULL) { |
| 550 | status = PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_SERVER_ERROR); |
| 551 | } |
| 552 | |
| 553 | } |
| 554 | } |
| 555 | |
| 556 | /* Save the result */ |
| 557 | tsx_id = (pj_uint32_t*) tdata->msg->hdr.tsx_id; |
| 558 | test_id = tsx_id[2]; |
| 559 | |
| 560 | if (test_id >= ST_MAX) { |
| 561 | PJ_LOG(4,(sess->pool->obj_name, "Invalid transaction ID %u in response", |
| 562 | test_id)); |
| 563 | end_session(sess, PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_SERVER_ERROR), |
| 564 | PJ_STUN_NAT_TYPE_ERR_UNKNOWN); |
| 565 | goto on_return; |
| 566 | } |
| 567 | |
| 568 | PJ_LOG(5,(sess->pool->obj_name, "Completed %s, status=%d", |
| 569 | test_names[test_id], status)); |
| 570 | |
| 571 | sess->result[test_id].complete = PJ_TRUE; |
| 572 | sess->result[test_id].status = status; |
| 573 | if (status == PJ_SUCCESS) { |
| 574 | pj_memcpy(&sess->result[test_id].ma, &mattr->sockaddr.ipv4, |
| 575 | sizeof(pj_sockaddr_in)); |
| 576 | pj_memcpy(&sess->result[test_id].ca, &ca->sockaddr.ipv4, |
| 577 | sizeof(pj_sockaddr_in)); |
| 578 | } |
| 579 | |
| 580 | /* Send Test 1B only when Test 2 completes. Must not send Test 1B |
| 581 | * before Test 2 completes to avoid creating mapping on the NAT. |
| 582 | */ |
| 583 | if (!sess->result[ST_TEST_1B].executed && |
| 584 | sess->result[ST_TEST_2].complete && |
| 585 | sess->result[ST_TEST_2].status != PJ_SUCCESS && |
| 586 | sess->result[ST_TEST_1].complete && |
| 587 | sess->result[ST_TEST_1].status == PJ_SUCCESS) |
| 588 | { |
| 589 | cmp = pj_memcmp(&sess->local_addr, &sess->result[ST_TEST_1].ma, |
| 590 | sizeof(pj_sockaddr_in)); |
| 591 | if (cmp != 0) |
| 592 | send_test(sess, ST_TEST_1B, &sess->result[ST_TEST_1].ca, 0); |
| 593 | } |
| 594 | |
| 595 | if (test_completed(sess)<3 || test_completed(sess)!=test_executed(sess)) |
| 596 | goto on_return; |
| 597 | |
| 598 | /* Handle the test result according to RFC 3489 page 22: |
| 599 | |
| 600 | |
| 601 | +--------+ |
| 602 | | Test | |
| 603 | | 1 | |
| 604 | +--------+ |
| 605 | | |
| 606 | | |
| 607 | V |
| 608 | /\ /\ |
| 609 | N / \ Y / \ Y +--------+ |
| 610 | UDP <-------/Resp\--------->/ IP \------------->| Test | |
| 611 | Blocked \ ? / \Same/ | 2 | |
| 612 | \ / \? / +--------+ |
| 613 | \/ \/ | |
| 614 | | N | |
| 615 | | V |
| 616 | V /\ |
| 617 | +--------+ Sym. N / \ |
| 618 | | Test | UDP <---/Resp\ |
| 619 | | 2 | Firewall \ ? / |
| 620 | +--------+ \ / |
| 621 | | \/ |
| 622 | V |Y |
| 623 | /\ /\ | |
| 624 | Symmetric N / \ +--------+ N / \ V |
| 625 | NAT <--- / IP \<-----| Test |<--- /Resp\ Open |
| 626 | \Same/ | 1B | \ ? / Internet |
| 627 | \? / +--------+ \ / |
| 628 | \/ \/ |
| 629 | | |Y |
| 630 | | | |
| 631 | | V |
| 632 | | Full |
| 633 | | Cone |
| 634 | V /\ |
| 635 | +--------+ / \ Y |
| 636 | | Test |------>/Resp\---->Restricted |
| 637 | | 3 | \ ? / |
| 638 | +--------+ \ / |
| 639 | \/ |
| 640 | |N |
| 641 | | Port |
| 642 | +------>Restricted |
| 643 | |
| 644 | Figure 2: Flow for type discovery process |
| 645 | */ |
| 646 | |
| 647 | switch (sess->result[ST_TEST_1].status) { |
| 648 | case PJNATH_ESTUNTIMEDOUT: |
| 649 | /* |
| 650 | * Test 1 has timed-out. Conclude with NAT_TYPE_BLOCKED. |
| 651 | */ |
| 652 | end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_BLOCKED); |
| 653 | break; |
| 654 | case PJ_SUCCESS: |
| 655 | /* |
| 656 | * Test 1 is successful. Further tests are needed to detect |
| 657 | * NAT type. Compare the MAPPED-ADDRESS with the local address. |
| 658 | */ |
| 659 | cmp = pj_memcmp(&sess->local_addr, &sess->result[ST_TEST_1].ma, |
| 660 | sizeof(pj_sockaddr_in)); |
| 661 | if (cmp==0) { |
| 662 | /* |
| 663 | * MAPPED-ADDRESS and local address is equal. Need one more |
| 664 | * test to determine NAT type. |
| 665 | */ |
| 666 | switch (sess->result[ST_TEST_2].status) { |
| 667 | case PJ_SUCCESS: |
| 668 | /* |
| 669 | * Test 2 is also successful. We're in the open. |
| 670 | */ |
| 671 | end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_OPEN); |
| 672 | break; |
| 673 | case PJNATH_ESTUNTIMEDOUT: |
| 674 | /* |
| 675 | * Test 2 has timed out. We're behind somekind of UDP |
| 676 | * firewall. |
| 677 | */ |
| 678 | end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_SYMMETRIC_UDP); |
| 679 | break; |
| 680 | default: |
| 681 | /* |
| 682 | * We've got other error with Test 2. |
| 683 | */ |
| 684 | end_session(sess, sess->result[ST_TEST_2].status, |
| 685 | PJ_STUN_NAT_TYPE_ERR_UNKNOWN); |
| 686 | break; |
| 687 | } |
| 688 | } else { |
| 689 | /* |
| 690 | * MAPPED-ADDRESS is different than local address. |
| 691 | * We're behind NAT. |
| 692 | */ |
| 693 | switch (sess->result[ST_TEST_2].status) { |
| 694 | case PJ_SUCCESS: |
| 695 | /* |
| 696 | * Test 2 is successful. We're behind a full-cone NAT. |
| 697 | */ |
| 698 | end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_FULL_CONE); |
| 699 | break; |
| 700 | case PJNATH_ESTUNTIMEDOUT: |
| 701 | /* |
| 702 | * Test 2 has timed-out Check result of test 1B.. |
| 703 | */ |
| 704 | switch (sess->result[ST_TEST_1B].status) { |
| 705 | case PJ_SUCCESS: |
| 706 | /* |
| 707 | * Compare the MAPPED-ADDRESS of test 1B with the |
| 708 | * MAPPED-ADDRESS returned in test 1.. |
| 709 | */ |
| 710 | cmp = pj_memcmp(&sess->result[ST_TEST_1].ma, |
| 711 | &sess->result[ST_TEST_1B].ma, |
| 712 | sizeof(pj_sockaddr_in)); |
| 713 | if (cmp != 0) { |
| 714 | /* |
| 715 | * MAPPED-ADDRESS is different, we're behind a |
| 716 | * symmetric NAT. |
| 717 | */ |
| 718 | end_session(sess, PJ_SUCCESS, |
| 719 | PJ_STUN_NAT_TYPE_SYMMETRIC); |
| 720 | } else { |
| 721 | /* |
| 722 | * MAPPED-ADDRESS is equal. We're behind a restricted |
| 723 | * or port-restricted NAT, depending on the result of |
| 724 | * test 3. |
| 725 | */ |
| 726 | switch (sess->result[ST_TEST_3].status) { |
| 727 | case PJ_SUCCESS: |
| 728 | /* |
| 729 | * Test 3 is successful, we're behind a restricted |
| 730 | * NAT. |
| 731 | */ |
| 732 | end_session(sess, PJ_SUCCESS, |
| 733 | PJ_STUN_NAT_TYPE_RESTRICTED); |
| 734 | break; |
| 735 | case PJNATH_ESTUNTIMEDOUT: |
| 736 | /* |
| 737 | * Test 3 failed, we're behind a port restricted |
| 738 | * NAT. |
| 739 | */ |
| 740 | end_session(sess, PJ_SUCCESS, |
| 741 | PJ_STUN_NAT_TYPE_PORT_RESTRICTED); |
| 742 | break; |
| 743 | default: |
| 744 | /* |
| 745 | * Got other error with test 3. |
| 746 | */ |
| 747 | end_session(sess, sess->result[ST_TEST_3].status, |
| 748 | PJ_STUN_NAT_TYPE_ERR_UNKNOWN); |
| 749 | break; |
| 750 | } |
| 751 | } |
| 752 | break; |
| 753 | case PJNATH_ESTUNTIMEDOUT: |
| 754 | /* |
| 755 | * Strangely test 1B has failed. Maybe connectivity was |
| 756 | * lost? Or perhaps port 3489 (the usual port number in |
| 757 | * CHANGED-ADDRESS) is blocked? |
| 758 | */ |
| 759 | switch (sess->result[ST_TEST_3].status) { |
| 760 | case PJ_SUCCESS: |
| 761 | /* Although test 1B failed, test 3 was successful. |
| 762 | * It could be that port 3489 is blocked, while the |
| 763 | * NAT itself looks to be a Restricted one. |
| 764 | */ |
| 765 | end_session(sess, PJ_SUCCESS, |
| 766 | PJ_STUN_NAT_TYPE_RESTRICTED); |
| 767 | break; |
| 768 | default: |
| 769 | /* Can't distinguish between Symmetric and Port |
| 770 | * Restricted, so set the type to Unknown |
| 771 | */ |
| 772 | end_session(sess, PJ_SUCCESS, |
| 773 | PJ_STUN_NAT_TYPE_ERR_UNKNOWN); |
| 774 | break; |
| 775 | } |
| 776 | break; |
| 777 | default: |
| 778 | /* |
| 779 | * Got other error with test 1B. |
| 780 | */ |
| 781 | end_session(sess, sess->result[ST_TEST_1B].status, |
| 782 | PJ_STUN_NAT_TYPE_ERR_UNKNOWN); |
| 783 | break; |
| 784 | } |
| 785 | break; |
| 786 | default: |
| 787 | /* |
| 788 | * We've got other error with Test 2. |
| 789 | */ |
| 790 | end_session(sess, sess->result[ST_TEST_2].status, |
| 791 | PJ_STUN_NAT_TYPE_ERR_UNKNOWN); |
| 792 | break; |
| 793 | } |
| 794 | } |
| 795 | break; |
| 796 | default: |
| 797 | /* |
| 798 | * We've got other error with Test 1. |
| 799 | */ |
| 800 | end_session(sess, sess->result[ST_TEST_1].status, |
| 801 | PJ_STUN_NAT_TYPE_ERR_UNKNOWN); |
| 802 | break; |
| 803 | } |
| 804 | |
| 805 | on_return: |
| 806 | pj_grp_lock_release(sess->grp_lock); |
| 807 | } |
| 808 | |
| 809 | |
| 810 | /* Perform test */ |
| 811 | static pj_status_t send_test(nat_detect_session *sess, |
| 812 | enum test_type test_id, |
| 813 | const pj_sockaddr_in *alt_addr, |
| 814 | pj_uint32_t change_flag) |
| 815 | { |
| 816 | pj_uint32_t magic, tsx_id[3]; |
| 817 | pj_status_t status; |
| 818 | |
| 819 | sess->result[test_id].executed = PJ_TRUE; |
| 820 | |
| 821 | /* Randomize tsx id */ |
| 822 | do { |
| 823 | magic = pj_rand(); |
| 824 | } while (magic == PJ_STUN_MAGIC); |
| 825 | |
| 826 | tsx_id[0] = pj_rand(); |
| 827 | tsx_id[1] = pj_rand(); |
| 828 | tsx_id[2] = test_id; |
| 829 | |
| 830 | /* Create BIND request */ |
| 831 | status = pj_stun_session_create_req(sess->stun_sess, |
| 832 | PJ_STUN_BINDING_REQUEST, magic, |
| 833 | (pj_uint8_t*)tsx_id, |
| 834 | &sess->result[test_id].tdata); |
| 835 | if (status != PJ_SUCCESS) |
| 836 | goto on_error; |
| 837 | |
| 838 | /* Add CHANGE-REQUEST attribute */ |
| 839 | status = pj_stun_msg_add_uint_attr(sess->pool, |
| 840 | sess->result[test_id].tdata->msg, |
| 841 | PJ_STUN_ATTR_CHANGE_REQUEST, |
| 842 | change_flag); |
| 843 | if (status != PJ_SUCCESS) |
| 844 | goto on_error; |
| 845 | |
| 846 | /* Configure alternate address */ |
| 847 | if (alt_addr) |
| 848 | sess->cur_server = (pj_sockaddr_in*) alt_addr; |
| 849 | else |
| 850 | sess->cur_server = &sess->server; |
| 851 | |
| 852 | PJ_LOG(5,(sess->pool->obj_name, |
| 853 | "Performing %s to %s:%d", |
| 854 | test_names[test_id], |
| 855 | pj_inet_ntoa(sess->cur_server->sin_addr), |
| 856 | pj_ntohs(sess->cur_server->sin_port))); |
| 857 | |
| 858 | /* Send the request */ |
| 859 | status = pj_stun_session_send_msg(sess->stun_sess, NULL, PJ_TRUE, |
| 860 | PJ_TRUE, sess->cur_server, |
| 861 | sizeof(pj_sockaddr_in), |
| 862 | sess->result[test_id].tdata); |
| 863 | if (status != PJ_SUCCESS) |
| 864 | goto on_error; |
| 865 | |
| 866 | return PJ_SUCCESS; |
| 867 | |
| 868 | on_error: |
| 869 | sess->result[test_id].complete = PJ_TRUE; |
| 870 | sess->result[test_id].status = status; |
| 871 | |
| 872 | return status; |
| 873 | } |
| 874 | |
| 875 | |
| 876 | /* Timer callback */ |
| 877 | static void on_sess_timer(pj_timer_heap_t *th, |
| 878 | pj_timer_entry *te) |
| 879 | { |
| 880 | nat_detect_session *sess; |
| 881 | |
| 882 | sess = (nat_detect_session*) te->user_data; |
| 883 | |
| 884 | if (te->id == TIMER_DESTROY) { |
| 885 | pj_grp_lock_acquire(sess->grp_lock); |
| 886 | pj_ioqueue_unregister(sess->key); |
| 887 | sess->key = NULL; |
| 888 | sess->sock = PJ_INVALID_SOCKET; |
| 889 | te->id = 0; |
| 890 | pj_grp_lock_release(sess->grp_lock); |
| 891 | |
| 892 | sess_destroy(sess); |
| 893 | |
| 894 | } else if (te->id == TIMER_TEST) { |
| 895 | |
| 896 | pj_bool_t next_timer; |
| 897 | |
| 898 | pj_grp_lock_acquire(sess->grp_lock); |
| 899 | |
| 900 | next_timer = PJ_FALSE; |
| 901 | |
| 902 | if (sess->timer_executed == 0) { |
| 903 | send_test(sess, ST_TEST_1, NULL, 0); |
| 904 | next_timer = PJ_TRUE; |
| 905 | } else if (sess->timer_executed == 1) { |
| 906 | send_test(sess, ST_TEST_2, NULL, CHANGE_IP_PORT_FLAG); |
| 907 | next_timer = PJ_TRUE; |
| 908 | } else if (sess->timer_executed == 2) { |
| 909 | send_test(sess, ST_TEST_3, NULL, CHANGE_PORT_FLAG); |
| 910 | } else { |
| 911 | pj_assert(!"Shouldn't have timer at this state"); |
| 912 | } |
| 913 | |
| 914 | ++sess->timer_executed; |
| 915 | |
| 916 | if (next_timer) { |
| 917 | pj_time_val delay = {0, TEST_INTERVAL}; |
| 918 | pj_timer_heap_schedule(th, te, &delay); |
| 919 | } else { |
| 920 | te->id = 0; |
| 921 | } |
| 922 | |
| 923 | pj_grp_lock_release(sess->grp_lock); |
| 924 | |
| 925 | } else { |
| 926 | pj_assert(!"Invalid timer ID"); |
| 927 | } |
| 928 | } |
| 929 | |