Tristan Matthews | 0a329cc | 2013-07-17 13:20:14 -0400 | [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_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, NULL, &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, (pj_status_t)-bytes_read, |
| 414 | PJ_STUN_NAT_TYPE_ERR_UNKNOWN); |
| 415 | goto on_return; |
| 416 | } |
| 417 | |
| 418 | } else if (bytes_read > 0) { |
| 419 | pj_stun_session_on_rx_pkt(sess->stun_sess, sess->rx_pkt, bytes_read, |
| 420 | PJ_STUN_IS_DATAGRAM|PJ_STUN_CHECK_PACKET, |
| 421 | NULL, NULL, |
| 422 | &sess->src_addr, sess->src_addr_len); |
| 423 | } |
| 424 | |
| 425 | |
| 426 | sess->rx_pkt_len = sizeof(sess->rx_pkt); |
| 427 | sess->src_addr_len = sizeof(sess->src_addr); |
| 428 | status = pj_ioqueue_recvfrom(key, op_key, sess->rx_pkt, &sess->rx_pkt_len, |
| 429 | PJ_IOQUEUE_ALWAYS_ASYNC, |
| 430 | &sess->src_addr, &sess->src_addr_len); |
| 431 | |
| 432 | if (status != PJ_EPENDING) { |
| 433 | pj_assert(status != PJ_SUCCESS); |
| 434 | end_session(sess, status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); |
| 435 | } |
| 436 | |
| 437 | on_return: |
| 438 | pj_mutex_unlock(sess->mutex); |
| 439 | } |
| 440 | |
| 441 | |
| 442 | /* |
| 443 | * Callback to send outgoing packet from STUN session. |
| 444 | */ |
| 445 | static pj_status_t on_send_msg(pj_stun_session *stun_sess, |
| 446 | void *token, |
| 447 | const void *pkt, |
| 448 | pj_size_t pkt_size, |
| 449 | const pj_sockaddr_t *dst_addr, |
| 450 | unsigned addr_len) |
| 451 | { |
| 452 | nat_detect_session *sess; |
| 453 | pj_ssize_t pkt_len; |
| 454 | pj_status_t status; |
| 455 | |
| 456 | PJ_UNUSED_ARG(token); |
| 457 | |
| 458 | sess = (nat_detect_session*) pj_stun_session_get_user_data(stun_sess); |
| 459 | |
| 460 | pkt_len = pkt_size; |
| 461 | status = pj_ioqueue_sendto(sess->key, &sess->write_op, pkt, &pkt_len, 0, |
| 462 | dst_addr, addr_len); |
| 463 | |
| 464 | return status; |
| 465 | |
| 466 | } |
| 467 | |
| 468 | /* |
| 469 | * Callback upon request completion. |
| 470 | */ |
| 471 | static void on_request_complete(pj_stun_session *stun_sess, |
| 472 | pj_status_t status, |
| 473 | void *token, |
| 474 | pj_stun_tx_data *tdata, |
| 475 | const pj_stun_msg *response, |
| 476 | const pj_sockaddr_t *src_addr, |
| 477 | unsigned src_addr_len) |
| 478 | { |
| 479 | nat_detect_session *sess; |
| 480 | pj_stun_sockaddr_attr *mattr = NULL; |
| 481 | pj_stun_changed_addr_attr *ca = NULL; |
| 482 | pj_uint32_t *tsx_id; |
| 483 | int cmp; |
| 484 | unsigned test_id; |
| 485 | |
| 486 | PJ_UNUSED_ARG(token); |
| 487 | PJ_UNUSED_ARG(tdata); |
| 488 | PJ_UNUSED_ARG(src_addr); |
| 489 | PJ_UNUSED_ARG(src_addr_len); |
| 490 | |
| 491 | sess = (nat_detect_session*) pj_stun_session_get_user_data(stun_sess); |
| 492 | |
| 493 | pj_mutex_lock(sess->mutex); |
| 494 | |
| 495 | /* Find errors in the response */ |
| 496 | if (status == PJ_SUCCESS) { |
| 497 | |
| 498 | /* Check error message */ |
| 499 | if (PJ_STUN_IS_ERROR_RESPONSE(response->hdr.type)) { |
| 500 | pj_stun_errcode_attr *eattr; |
| 501 | int err_code; |
| 502 | |
| 503 | eattr = (pj_stun_errcode_attr*) |
| 504 | pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0); |
| 505 | |
| 506 | if (eattr != NULL) |
| 507 | err_code = eattr->err_code; |
| 508 | else |
| 509 | err_code = PJ_STUN_SC_SERVER_ERROR; |
| 510 | |
| 511 | status = PJ_STATUS_FROM_STUN_CODE(err_code); |
| 512 | |
| 513 | |
| 514 | } else { |
| 515 | |
| 516 | /* Get MAPPED-ADDRESS or XOR-MAPPED-ADDRESS */ |
| 517 | mattr = (pj_stun_sockaddr_attr*) |
| 518 | pj_stun_msg_find_attr(response, PJ_STUN_ATTR_XOR_MAPPED_ADDR, 0); |
| 519 | if (mattr == NULL) { |
| 520 | mattr = (pj_stun_sockaddr_attr*) |
| 521 | pj_stun_msg_find_attr(response, PJ_STUN_ATTR_MAPPED_ADDR, 0); |
| 522 | } |
| 523 | |
| 524 | if (mattr == NULL) { |
| 525 | status = PJNATH_ESTUNNOMAPPEDADDR; |
| 526 | } |
| 527 | |
| 528 | /* Get CHANGED-ADDRESS attribute */ |
| 529 | ca = (pj_stun_changed_addr_attr*) |
| 530 | pj_stun_msg_find_attr(response, PJ_STUN_ATTR_CHANGED_ADDR, 0); |
| 531 | |
| 532 | if (ca == NULL) { |
| 533 | status = PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_SERVER_ERROR); |
| 534 | } |
| 535 | |
| 536 | } |
| 537 | } |
| 538 | |
| 539 | /* Save the result */ |
| 540 | tsx_id = (pj_uint32_t*) tdata->msg->hdr.tsx_id; |
| 541 | test_id = tsx_id[2]; |
| 542 | |
| 543 | if (test_id >= ST_MAX) { |
| 544 | PJ_LOG(4,(sess->pool->obj_name, "Invalid transaction ID %u in response", |
| 545 | test_id)); |
| 546 | end_session(sess, PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_SERVER_ERROR), |
| 547 | PJ_STUN_NAT_TYPE_ERR_UNKNOWN); |
| 548 | goto on_return; |
| 549 | } |
| 550 | |
| 551 | PJ_LOG(5,(sess->pool->obj_name, "Completed %s, status=%d", |
| 552 | test_names[test_id], status)); |
| 553 | |
| 554 | sess->result[test_id].complete = PJ_TRUE; |
| 555 | sess->result[test_id].status = status; |
| 556 | if (status == PJ_SUCCESS) { |
| 557 | pj_memcpy(&sess->result[test_id].ma, &mattr->sockaddr.ipv4, |
| 558 | sizeof(pj_sockaddr_in)); |
| 559 | pj_memcpy(&sess->result[test_id].ca, &ca->sockaddr.ipv4, |
| 560 | sizeof(pj_sockaddr_in)); |
| 561 | } |
| 562 | |
| 563 | /* Send Test 1B only when Test 2 completes. Must not send Test 1B |
| 564 | * before Test 2 completes to avoid creating mapping on the NAT. |
| 565 | */ |
| 566 | if (!sess->result[ST_TEST_1B].executed && |
| 567 | sess->result[ST_TEST_2].complete && |
| 568 | sess->result[ST_TEST_2].status != PJ_SUCCESS && |
| 569 | sess->result[ST_TEST_1].complete && |
| 570 | sess->result[ST_TEST_1].status == PJ_SUCCESS) |
| 571 | { |
| 572 | cmp = pj_memcmp(&sess->local_addr, &sess->result[ST_TEST_1].ma, |
| 573 | sizeof(pj_sockaddr_in)); |
| 574 | if (cmp != 0) |
| 575 | send_test(sess, ST_TEST_1B, &sess->result[ST_TEST_1].ca, 0); |
| 576 | } |
| 577 | |
| 578 | if (test_completed(sess)<3 || test_completed(sess)!=test_executed(sess)) |
| 579 | goto on_return; |
| 580 | |
| 581 | /* Handle the test result according to RFC 3489 page 22: |
| 582 | |
| 583 | |
| 584 | +--------+ |
| 585 | | Test | |
| 586 | | 1 | |
| 587 | +--------+ |
| 588 | | |
| 589 | | |
| 590 | V |
| 591 | /\ /\ |
| 592 | N / \ Y / \ Y +--------+ |
| 593 | UDP <-------/Resp\--------->/ IP \------------->| Test | |
| 594 | Blocked \ ? / \Same/ | 2 | |
| 595 | \ / \? / +--------+ |
| 596 | \/ \/ | |
| 597 | | N | |
| 598 | | V |
| 599 | V /\ |
| 600 | +--------+ Sym. N / \ |
| 601 | | Test | UDP <---/Resp\ |
| 602 | | 2 | Firewall \ ? / |
| 603 | +--------+ \ / |
| 604 | | \/ |
| 605 | V |Y |
| 606 | /\ /\ | |
| 607 | Symmetric N / \ +--------+ N / \ V |
| 608 | NAT <--- / IP \<-----| Test |<--- /Resp\ Open |
| 609 | \Same/ | 1B | \ ? / Internet |
| 610 | \? / +--------+ \ / |
| 611 | \/ \/ |
| 612 | | |Y |
| 613 | | | |
| 614 | | V |
| 615 | | Full |
| 616 | | Cone |
| 617 | V /\ |
| 618 | +--------+ / \ Y |
| 619 | | Test |------>/Resp\---->Restricted |
| 620 | | 3 | \ ? / |
| 621 | +--------+ \ / |
| 622 | \/ |
| 623 | |N |
| 624 | | Port |
| 625 | +------>Restricted |
| 626 | |
| 627 | Figure 2: Flow for type discovery process |
| 628 | */ |
| 629 | |
| 630 | switch (sess->result[ST_TEST_1].status) { |
| 631 | case PJNATH_ESTUNTIMEDOUT: |
| 632 | /* |
| 633 | * Test 1 has timed-out. Conclude with NAT_TYPE_BLOCKED. |
| 634 | */ |
| 635 | end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_BLOCKED); |
| 636 | break; |
| 637 | case PJ_SUCCESS: |
| 638 | /* |
| 639 | * Test 1 is successful. Further tests are needed to detect |
| 640 | * NAT type. Compare the MAPPED-ADDRESS with the local address. |
| 641 | */ |
| 642 | cmp = pj_memcmp(&sess->local_addr, &sess->result[ST_TEST_1].ma, |
| 643 | sizeof(pj_sockaddr_in)); |
| 644 | if (cmp==0) { |
| 645 | /* |
| 646 | * MAPPED-ADDRESS and local address is equal. Need one more |
| 647 | * test to determine NAT type. |
| 648 | */ |
| 649 | switch (sess->result[ST_TEST_2].status) { |
| 650 | case PJ_SUCCESS: |
| 651 | /* |
| 652 | * Test 2 is also successful. We're in the open. |
| 653 | */ |
| 654 | end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_OPEN); |
| 655 | break; |
| 656 | case PJNATH_ESTUNTIMEDOUT: |
| 657 | /* |
| 658 | * Test 2 has timed out. We're behind somekind of UDP |
| 659 | * firewall. |
| 660 | */ |
| 661 | end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_SYMMETRIC_UDP); |
| 662 | break; |
| 663 | default: |
| 664 | /* |
| 665 | * We've got other error with Test 2. |
| 666 | */ |
| 667 | end_session(sess, sess->result[ST_TEST_2].status, |
| 668 | PJ_STUN_NAT_TYPE_ERR_UNKNOWN); |
| 669 | break; |
| 670 | } |
| 671 | } else { |
| 672 | /* |
| 673 | * MAPPED-ADDRESS is different than local address. |
| 674 | * We're behind NAT. |
| 675 | */ |
| 676 | switch (sess->result[ST_TEST_2].status) { |
| 677 | case PJ_SUCCESS: |
| 678 | /* |
| 679 | * Test 2 is successful. We're behind a full-cone NAT. |
| 680 | */ |
| 681 | end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_FULL_CONE); |
| 682 | break; |
| 683 | case PJNATH_ESTUNTIMEDOUT: |
| 684 | /* |
| 685 | * Test 2 has timed-out Check result of test 1B.. |
| 686 | */ |
| 687 | switch (sess->result[ST_TEST_1B].status) { |
| 688 | case PJ_SUCCESS: |
| 689 | /* |
| 690 | * Compare the MAPPED-ADDRESS of test 1B with the |
| 691 | * MAPPED-ADDRESS returned in test 1.. |
| 692 | */ |
| 693 | cmp = pj_memcmp(&sess->result[ST_TEST_1].ma, |
| 694 | &sess->result[ST_TEST_1B].ma, |
| 695 | sizeof(pj_sockaddr_in)); |
| 696 | if (cmp != 0) { |
| 697 | /* |
| 698 | * MAPPED-ADDRESS is different, we're behind a |
| 699 | * symmetric NAT. |
| 700 | */ |
| 701 | end_session(sess, PJ_SUCCESS, |
| 702 | PJ_STUN_NAT_TYPE_SYMMETRIC); |
| 703 | } else { |
| 704 | /* |
| 705 | * MAPPED-ADDRESS is equal. We're behind a restricted |
| 706 | * or port-restricted NAT, depending on the result of |
| 707 | * test 3. |
| 708 | */ |
| 709 | switch (sess->result[ST_TEST_3].status) { |
| 710 | case PJ_SUCCESS: |
| 711 | /* |
| 712 | * Test 3 is successful, we're behind a restricted |
| 713 | * NAT. |
| 714 | */ |
| 715 | end_session(sess, PJ_SUCCESS, |
| 716 | PJ_STUN_NAT_TYPE_RESTRICTED); |
| 717 | break; |
| 718 | case PJNATH_ESTUNTIMEDOUT: |
| 719 | /* |
| 720 | * Test 3 failed, we're behind a port restricted |
| 721 | * NAT. |
| 722 | */ |
| 723 | end_session(sess, PJ_SUCCESS, |
| 724 | PJ_STUN_NAT_TYPE_PORT_RESTRICTED); |
| 725 | break; |
| 726 | default: |
| 727 | /* |
| 728 | * Got other error with test 3. |
| 729 | */ |
| 730 | end_session(sess, sess->result[ST_TEST_3].status, |
| 731 | PJ_STUN_NAT_TYPE_ERR_UNKNOWN); |
| 732 | break; |
| 733 | } |
| 734 | } |
| 735 | break; |
| 736 | case PJNATH_ESTUNTIMEDOUT: |
| 737 | /* |
| 738 | * Strangely test 1B has failed. Maybe connectivity was |
| 739 | * lost? Or perhaps port 3489 (the usual port number in |
| 740 | * CHANGED-ADDRESS) is blocked? |
| 741 | */ |
| 742 | switch (sess->result[ST_TEST_3].status) { |
| 743 | case PJ_SUCCESS: |
| 744 | /* Although test 1B failed, test 3 was successful. |
| 745 | * It could be that port 3489 is blocked, while the |
| 746 | * NAT itself looks to be a Restricted one. |
| 747 | */ |
| 748 | end_session(sess, PJ_SUCCESS, |
| 749 | PJ_STUN_NAT_TYPE_RESTRICTED); |
| 750 | break; |
| 751 | default: |
| 752 | /* Can't distinguish between Symmetric and Port |
| 753 | * Restricted, so set the type to Unknown |
| 754 | */ |
| 755 | end_session(sess, PJ_SUCCESS, |
| 756 | PJ_STUN_NAT_TYPE_ERR_UNKNOWN); |
| 757 | break; |
| 758 | } |
| 759 | break; |
| 760 | default: |
| 761 | /* |
| 762 | * Got other error with test 1B. |
| 763 | */ |
| 764 | end_session(sess, sess->result[ST_TEST_1B].status, |
| 765 | PJ_STUN_NAT_TYPE_ERR_UNKNOWN); |
| 766 | break; |
| 767 | } |
| 768 | break; |
| 769 | default: |
| 770 | /* |
| 771 | * We've got other error with Test 2. |
| 772 | */ |
| 773 | end_session(sess, sess->result[ST_TEST_2].status, |
| 774 | PJ_STUN_NAT_TYPE_ERR_UNKNOWN); |
| 775 | break; |
| 776 | } |
| 777 | } |
| 778 | break; |
| 779 | default: |
| 780 | /* |
| 781 | * We've got other error with Test 1. |
| 782 | */ |
| 783 | end_session(sess, sess->result[ST_TEST_1].status, |
| 784 | PJ_STUN_NAT_TYPE_ERR_UNKNOWN); |
| 785 | break; |
| 786 | } |
| 787 | |
| 788 | on_return: |
| 789 | pj_mutex_unlock(sess->mutex); |
| 790 | } |
| 791 | |
| 792 | |
| 793 | /* Perform test */ |
| 794 | static pj_status_t send_test(nat_detect_session *sess, |
| 795 | enum test_type test_id, |
| 796 | const pj_sockaddr_in *alt_addr, |
| 797 | pj_uint32_t change_flag) |
| 798 | { |
| 799 | pj_uint32_t magic, tsx_id[3]; |
| 800 | pj_status_t status; |
| 801 | |
| 802 | sess->result[test_id].executed = PJ_TRUE; |
| 803 | |
| 804 | /* Randomize tsx id */ |
| 805 | do { |
| 806 | magic = pj_rand(); |
| 807 | } while (magic == PJ_STUN_MAGIC); |
| 808 | |
| 809 | tsx_id[0] = pj_rand(); |
| 810 | tsx_id[1] = pj_rand(); |
| 811 | tsx_id[2] = test_id; |
| 812 | |
| 813 | /* Create BIND request */ |
| 814 | status = pj_stun_session_create_req(sess->stun_sess, |
| 815 | PJ_STUN_BINDING_REQUEST, magic, |
| 816 | (pj_uint8_t*)tsx_id, |
| 817 | &sess->result[test_id].tdata); |
| 818 | if (status != PJ_SUCCESS) |
| 819 | goto on_error; |
| 820 | |
| 821 | /* Add CHANGE-REQUEST attribute */ |
| 822 | status = pj_stun_msg_add_uint_attr(sess->pool, |
| 823 | sess->result[test_id].tdata->msg, |
| 824 | PJ_STUN_ATTR_CHANGE_REQUEST, |
| 825 | change_flag); |
| 826 | if (status != PJ_SUCCESS) |
| 827 | goto on_error; |
| 828 | |
| 829 | /* Configure alternate address */ |
| 830 | if (alt_addr) |
| 831 | sess->cur_server = (pj_sockaddr_in*) alt_addr; |
| 832 | else |
| 833 | sess->cur_server = &sess->server; |
| 834 | |
| 835 | PJ_LOG(5,(sess->pool->obj_name, |
| 836 | "Performing %s to %s:%d", |
| 837 | test_names[test_id], |
| 838 | pj_inet_ntoa(sess->cur_server->sin_addr), |
| 839 | pj_ntohs(sess->cur_server->sin_port))); |
| 840 | |
| 841 | /* Send the request */ |
| 842 | status = pj_stun_session_send_msg(sess->stun_sess, NULL, PJ_TRUE, |
| 843 | PJ_TRUE, sess->cur_server, |
| 844 | sizeof(pj_sockaddr_in), |
| 845 | sess->result[test_id].tdata); |
| 846 | if (status != PJ_SUCCESS) |
| 847 | goto on_error; |
| 848 | |
| 849 | return PJ_SUCCESS; |
| 850 | |
| 851 | on_error: |
| 852 | sess->result[test_id].complete = PJ_TRUE; |
| 853 | sess->result[test_id].status = status; |
| 854 | |
| 855 | return status; |
| 856 | } |
| 857 | |
| 858 | |
| 859 | /* Timer callback */ |
| 860 | static void on_sess_timer(pj_timer_heap_t *th, |
| 861 | pj_timer_entry *te) |
| 862 | { |
| 863 | nat_detect_session *sess; |
| 864 | |
| 865 | sess = (nat_detect_session*) te->user_data; |
| 866 | |
| 867 | if (te->id == TIMER_DESTROY) { |
| 868 | pj_mutex_lock(sess->mutex); |
| 869 | pj_ioqueue_unregister(sess->key); |
| 870 | sess->key = NULL; |
| 871 | sess->sock = PJ_INVALID_SOCKET; |
| 872 | te->id = 0; |
| 873 | pj_mutex_unlock(sess->mutex); |
| 874 | |
| 875 | sess_destroy(sess); |
| 876 | |
| 877 | } else if (te->id == TIMER_TEST) { |
| 878 | |
| 879 | pj_bool_t next_timer; |
| 880 | |
| 881 | pj_mutex_lock(sess->mutex); |
| 882 | |
| 883 | next_timer = PJ_FALSE; |
| 884 | |
| 885 | if (sess->timer_executed == 0) { |
| 886 | send_test(sess, ST_TEST_1, NULL, 0); |
| 887 | next_timer = PJ_TRUE; |
| 888 | } else if (sess->timer_executed == 1) { |
| 889 | send_test(sess, ST_TEST_2, NULL, CHANGE_IP_PORT_FLAG); |
| 890 | next_timer = PJ_TRUE; |
| 891 | } else if (sess->timer_executed == 2) { |
| 892 | send_test(sess, ST_TEST_3, NULL, CHANGE_PORT_FLAG); |
| 893 | } else { |
| 894 | pj_assert(!"Shouldn't have timer at this state"); |
| 895 | } |
| 896 | |
| 897 | ++sess->timer_executed; |
| 898 | |
| 899 | if (next_timer) { |
| 900 | pj_time_val delay = {0, TEST_INTERVAL}; |
| 901 | pj_timer_heap_schedule(th, te, &delay); |
| 902 | } else { |
| 903 | te->id = 0; |
| 904 | } |
| 905 | |
| 906 | pj_mutex_unlock(sess->mutex); |
| 907 | |
| 908 | } else { |
| 909 | pj_assert(!"Invalid timer ID"); |
| 910 | } |
| 911 | } |
| 912 | |