diff options
author | David M. Lee <dlee@digium.com> | 2013-01-07 14:24:28 -0600 |
---|---|---|
committer | David M. Lee <dlee@digium.com> | 2013-01-07 14:24:28 -0600 |
commit | f3ab456a17af1c89a6e3be4d20c5944853df1cb0 (patch) | |
tree | d00e1a332cd038a6d906a1ea0ac91e1a4458e617 /pjnath/src/pjnath/nat_detect.c |
Import pjproject-2.0.1
Diffstat (limited to 'pjnath/src/pjnath/nat_detect.c')
-rw-r--r-- | pjnath/src/pjnath/nat_detect.c | 911 |
1 files changed, 911 insertions, 0 deletions
diff --git a/pjnath/src/pjnath/nat_detect.c b/pjnath/src/pjnath/nat_detect.c new file mode 100644 index 0000000..86ac694 --- /dev/null +++ b/pjnath/src/pjnath/nat_detect.c @@ -0,0 +1,911 @@ +/* $Id: nat_detect.c 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjnath/nat_detect.h> +#include <pjnath/errno.h> +#include <pj/assert.h> +#include <pj/ioqueue.h> +#include <pj/log.h> +#include <pj/os.h> +#include <pj/pool.h> +#include <pj/rand.h> +#include <pj/string.h> +#include <pj/timer.h> +#include <pj/compat/socket.h> + + +static const char *nat_type_names[] = +{ + "Unknown", + "ErrUnknown", + "Open", + "Blocked", + "Symmetric UDP", + "Full Cone", + "Symmetric", + "Restricted", + "Port Restricted" +}; + + +#define CHANGE_IP_FLAG 4 +#define CHANGE_PORT_FLAG 2 +#define CHANGE_IP_PORT_FLAG (CHANGE_IP_FLAG | CHANGE_PORT_FLAG) +#define TEST_INTERVAL 50 + +enum test_type +{ + ST_TEST_1, + ST_TEST_2, + ST_TEST_3, + ST_TEST_1B, + ST_MAX +}; + +static const char *test_names[] = +{ + "Test I: Binding request", + "Test II: Binding request with change address and port request", + "Test III: Binding request with change port request", + "Test IB: Binding request to alternate address" +}; + +enum timer_type +{ + TIMER_TEST = 1, + TIMER_DESTROY = 2 +}; + +typedef struct nat_detect_session +{ + pj_pool_t *pool; + pj_mutex_t *mutex; + + pj_timer_heap_t *timer_heap; + pj_timer_entry timer; + unsigned timer_executed; + + void *user_data; + pj_stun_nat_detect_cb *cb; + pj_sock_t sock; + pj_sockaddr_in local_addr; + pj_ioqueue_key_t *key; + pj_sockaddr_in server; + pj_sockaddr_in *cur_server; + pj_stun_session *stun_sess; + + pj_ioqueue_op_key_t read_op, write_op; + pj_uint8_t rx_pkt[PJ_STUN_MAX_PKT_LEN]; + pj_ssize_t rx_pkt_len; + pj_sockaddr_in src_addr; + int src_addr_len; + + struct result + { + pj_bool_t executed; + pj_bool_t complete; + pj_status_t status; + pj_sockaddr_in ma; + pj_sockaddr_in ca; + pj_stun_tx_data *tdata; + } result[ST_MAX]; + +} nat_detect_session; + + +static void on_read_complete(pj_ioqueue_key_t *key, + pj_ioqueue_op_key_t *op_key, + pj_ssize_t bytes_read); +static void on_request_complete(pj_stun_session *sess, + pj_status_t status, + void *token, + pj_stun_tx_data *tdata, + const pj_stun_msg *response, + const pj_sockaddr_t *src_addr, + unsigned src_addr_len); +static pj_status_t on_send_msg(pj_stun_session *sess, + void *token, + const void *pkt, + pj_size_t pkt_size, + const pj_sockaddr_t *dst_addr, + unsigned addr_len); + +static pj_status_t send_test(nat_detect_session *sess, + enum test_type test_id, + const pj_sockaddr_in *alt_addr, + pj_uint32_t change_flag); +static void on_sess_timer(pj_timer_heap_t *th, + pj_timer_entry *te); +static void sess_destroy(nat_detect_session *sess); + + +/* + * Get the NAT name from the specified NAT type. + */ +PJ_DEF(const char*) pj_stun_get_nat_name(pj_stun_nat_type type) +{ + PJ_ASSERT_RETURN(type >= 0 && type <= PJ_STUN_NAT_TYPE_PORT_RESTRICTED, + "*Invalid*"); + + return nat_type_names[type]; +} + +static int test_executed(nat_detect_session *sess) +{ + unsigned i, count; + for (i=0, count=0; i<PJ_ARRAY_SIZE(sess->result); ++i) { + if (sess->result[i].executed) + ++count; + } + return count; +} + +static int test_completed(nat_detect_session *sess) +{ + unsigned i, count; + for (i=0, count=0; i<PJ_ARRAY_SIZE(sess->result); ++i) { + if (sess->result[i].complete) + ++count; + } + return count; +} + +static pj_status_t get_local_interface(const pj_sockaddr_in *server, + pj_in_addr *local_addr) +{ + pj_sock_t sock; + pj_sockaddr_in tmp; + int addr_len; + pj_status_t status; + + status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock); + if (status != PJ_SUCCESS) + return status; + + status = pj_sock_bind_in(sock, 0, 0); + if (status != PJ_SUCCESS) { + pj_sock_close(sock); + return status; + } + + status = pj_sock_connect(sock, server, sizeof(pj_sockaddr_in)); + if (status != PJ_SUCCESS) { + pj_sock_close(sock); + return status; + } + + addr_len = sizeof(pj_sockaddr_in); + status = pj_sock_getsockname(sock, &tmp, &addr_len); + if (status != PJ_SUCCESS) { + pj_sock_close(sock); + return status; + } + + local_addr->s_addr = tmp.sin_addr.s_addr; + + pj_sock_close(sock); + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pj_stun_detect_nat_type(const pj_sockaddr_in *server, + pj_stun_config *stun_cfg, + void *user_data, + pj_stun_nat_detect_cb *cb) +{ + pj_pool_t *pool; + nat_detect_session *sess; + pj_stun_session_cb sess_cb; + pj_ioqueue_callback ioqueue_cb; + int addr_len; + pj_status_t status; + + PJ_ASSERT_RETURN(server && stun_cfg, PJ_EINVAL); + PJ_ASSERT_RETURN(stun_cfg->pf && stun_cfg->ioqueue && stun_cfg->timer_heap, + PJ_EINVAL); + + /* + * Init NAT detection session. + */ + pool = pj_pool_create(stun_cfg->pf, "natck%p", PJNATH_POOL_LEN_NATCK, + PJNATH_POOL_INC_NATCK, NULL); + if (!pool) + return PJ_ENOMEM; + + sess = PJ_POOL_ZALLOC_T(pool, nat_detect_session); + sess->pool = pool; + sess->user_data = user_data; + sess->cb = cb; + + status = pj_mutex_create_recursive(pool, pool->obj_name, &sess->mutex); + if (status != PJ_SUCCESS) + goto on_error; + + pj_memcpy(&sess->server, server, sizeof(pj_sockaddr_in)); + + /* + * Init timer to self-destroy. + */ + sess->timer_heap = stun_cfg->timer_heap; + sess->timer.cb = &on_sess_timer; + sess->timer.user_data = sess; + + + /* + * Initialize socket. + */ + status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sess->sock); + if (status != PJ_SUCCESS) + goto on_error; + + /* + * Bind to any. + */ + pj_bzero(&sess->local_addr, sizeof(pj_sockaddr_in)); + sess->local_addr.sin_family = pj_AF_INET(); + status = pj_sock_bind(sess->sock, &sess->local_addr, + sizeof(pj_sockaddr_in)); + if (status != PJ_SUCCESS) + goto on_error; + + /* + * Get local/bound address. + */ + addr_len = sizeof(sess->local_addr); + status = pj_sock_getsockname(sess->sock, &sess->local_addr, &addr_len); + if (status != PJ_SUCCESS) + goto on_error; + + /* + * Find out which interface is used to send to the server. + */ + status = get_local_interface(server, &sess->local_addr.sin_addr); + if (status != PJ_SUCCESS) + goto on_error; + + PJ_LOG(5,(sess->pool->obj_name, "Local address is %s:%d", + pj_inet_ntoa(sess->local_addr.sin_addr), + pj_ntohs(sess->local_addr.sin_port))); + + PJ_LOG(5,(sess->pool->obj_name, "Server set to %s:%d", + pj_inet_ntoa(server->sin_addr), + pj_ntohs(server->sin_port))); + + /* + * Register socket to ioqueue to receive asynchronous input + * notification. + */ + pj_bzero(&ioqueue_cb, sizeof(ioqueue_cb)); + ioqueue_cb.on_read_complete = &on_read_complete; + + status = pj_ioqueue_register_sock(sess->pool, stun_cfg->ioqueue, + sess->sock, sess, &ioqueue_cb, + &sess->key); + if (status != PJ_SUCCESS) + goto on_error; + + /* + * Create STUN session. + */ + pj_bzero(&sess_cb, sizeof(sess_cb)); + sess_cb.on_request_complete = &on_request_complete; + sess_cb.on_send_msg = &on_send_msg; + status = pj_stun_session_create(stun_cfg, pool->obj_name, &sess_cb, + PJ_FALSE, &sess->stun_sess); + if (status != PJ_SUCCESS) + goto on_error; + + pj_stun_session_set_user_data(sess->stun_sess, sess); + + /* + * Kick-off ioqueue reading. + */ + pj_ioqueue_op_key_init(&sess->read_op, sizeof(sess->read_op)); + pj_ioqueue_op_key_init(&sess->write_op, sizeof(sess->write_op)); + on_read_complete(sess->key, &sess->read_op, 0); + + /* + * Start TEST_1 + */ + sess->timer.id = TIMER_TEST; + on_sess_timer(stun_cfg->timer_heap, &sess->timer); + + return PJ_SUCCESS; + +on_error: + sess_destroy(sess); + return status; +} + + +static void sess_destroy(nat_detect_session *sess) +{ + if (sess->stun_sess) { + pj_stun_session_destroy(sess->stun_sess); + } + + if (sess->key) { + pj_ioqueue_unregister(sess->key); + } else if (sess->sock && sess->sock != PJ_INVALID_SOCKET) { + pj_sock_close(sess->sock); + } + + if (sess->mutex) { + pj_mutex_destroy(sess->mutex); + } + + if (sess->pool) { + pj_pool_release(sess->pool); + } +} + + +static void end_session(nat_detect_session *sess, + pj_status_t status, + pj_stun_nat_type nat_type) +{ + pj_stun_nat_detect_result result; + char errmsg[PJ_ERR_MSG_SIZE]; + pj_time_val delay; + + if (sess->timer.id != 0) { + pj_timer_heap_cancel(sess->timer_heap, &sess->timer); + sess->timer.id = 0; + } + + pj_bzero(&result, sizeof(result)); + errmsg[0] = '\0'; + result.status_text = errmsg; + + result.status = status; + pj_strerror(status, errmsg, sizeof(errmsg)); + result.nat_type = nat_type; + result.nat_type_name = nat_type_names[result.nat_type]; + + if (sess->cb) + (*sess->cb)(sess->user_data, &result); + + delay.sec = 0; + delay.msec = 0; + + sess->timer.id = TIMER_DESTROY; + pj_timer_heap_schedule(sess->timer_heap, &sess->timer, &delay); +} + + +/* + * Callback upon receiving packet from network. + */ +static void on_read_complete(pj_ioqueue_key_t *key, + pj_ioqueue_op_key_t *op_key, + pj_ssize_t bytes_read) +{ + nat_detect_session *sess; + pj_status_t status; + + sess = (nat_detect_session *) pj_ioqueue_get_user_data(key); + pj_assert(sess != NULL); + + pj_mutex_lock(sess->mutex); + + if (bytes_read < 0) { + if (-bytes_read != PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK) && + -bytes_read != PJ_STATUS_FROM_OS(OSERR_EINPROGRESS) && + -bytes_read != PJ_STATUS_FROM_OS(OSERR_ECONNRESET)) + { + /* Permanent error */ + end_session(sess, -bytes_read, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + goto on_return; + } + + } else if (bytes_read > 0) { + pj_stun_session_on_rx_pkt(sess->stun_sess, sess->rx_pkt, bytes_read, + PJ_STUN_IS_DATAGRAM|PJ_STUN_CHECK_PACKET, + NULL, NULL, + &sess->src_addr, sess->src_addr_len); + } + + + sess->rx_pkt_len = sizeof(sess->rx_pkt); + sess->src_addr_len = sizeof(sess->src_addr); + status = pj_ioqueue_recvfrom(key, op_key, sess->rx_pkt, &sess->rx_pkt_len, + PJ_IOQUEUE_ALWAYS_ASYNC, + &sess->src_addr, &sess->src_addr_len); + + if (status != PJ_EPENDING) { + pj_assert(status != PJ_SUCCESS); + end_session(sess, status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + } + +on_return: + pj_mutex_unlock(sess->mutex); +} + + +/* + * Callback to send outgoing packet from STUN session. + */ +static pj_status_t on_send_msg(pj_stun_session *stun_sess, + void *token, + const void *pkt, + pj_size_t pkt_size, + const pj_sockaddr_t *dst_addr, + unsigned addr_len) +{ + nat_detect_session *sess; + pj_ssize_t pkt_len; + pj_status_t status; + + PJ_UNUSED_ARG(token); + + sess = (nat_detect_session*) pj_stun_session_get_user_data(stun_sess); + + pkt_len = pkt_size; + status = pj_ioqueue_sendto(sess->key, &sess->write_op, pkt, &pkt_len, 0, + dst_addr, addr_len); + + return status; + +} + +/* + * Callback upon request completion. + */ +static void on_request_complete(pj_stun_session *stun_sess, + pj_status_t status, + void *token, + pj_stun_tx_data *tdata, + const pj_stun_msg *response, + const pj_sockaddr_t *src_addr, + unsigned src_addr_len) +{ + nat_detect_session *sess; + pj_stun_sockaddr_attr *mattr = NULL; + pj_stun_changed_addr_attr *ca = NULL; + pj_uint32_t *tsx_id; + int cmp; + unsigned test_id; + + PJ_UNUSED_ARG(token); + PJ_UNUSED_ARG(tdata); + PJ_UNUSED_ARG(src_addr); + PJ_UNUSED_ARG(src_addr_len); + + sess = (nat_detect_session*) pj_stun_session_get_user_data(stun_sess); + + pj_mutex_lock(sess->mutex); + + /* Find errors in the response */ + if (status == PJ_SUCCESS) { + + /* Check error message */ + if (PJ_STUN_IS_ERROR_RESPONSE(response->hdr.type)) { + pj_stun_errcode_attr *eattr; + int err_code; + + eattr = (pj_stun_errcode_attr*) + pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0); + + if (eattr != NULL) + err_code = eattr->err_code; + else + err_code = PJ_STUN_SC_SERVER_ERROR; + + status = PJ_STATUS_FROM_STUN_CODE(err_code); + + + } else { + + /* Get MAPPED-ADDRESS or XOR-MAPPED-ADDRESS */ + mattr = (pj_stun_sockaddr_attr*) + pj_stun_msg_find_attr(response, PJ_STUN_ATTR_XOR_MAPPED_ADDR, 0); + if (mattr == NULL) { + mattr = (pj_stun_sockaddr_attr*) + pj_stun_msg_find_attr(response, PJ_STUN_ATTR_MAPPED_ADDR, 0); + } + + if (mattr == NULL) { + status = PJNATH_ESTUNNOMAPPEDADDR; + } + + /* Get CHANGED-ADDRESS attribute */ + ca = (pj_stun_changed_addr_attr*) + pj_stun_msg_find_attr(response, PJ_STUN_ATTR_CHANGED_ADDR, 0); + + if (ca == NULL) { + status = PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_SERVER_ERROR); + } + + } + } + + /* Save the result */ + tsx_id = (pj_uint32_t*) tdata->msg->hdr.tsx_id; + test_id = tsx_id[2]; + + if (test_id >= ST_MAX) { + PJ_LOG(4,(sess->pool->obj_name, "Invalid transaction ID %u in response", + test_id)); + end_session(sess, PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_SERVER_ERROR), + PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + goto on_return; + } + + PJ_LOG(5,(sess->pool->obj_name, "Completed %s, status=%d", + test_names[test_id], status)); + + sess->result[test_id].complete = PJ_TRUE; + sess->result[test_id].status = status; + if (status == PJ_SUCCESS) { + pj_memcpy(&sess->result[test_id].ma, &mattr->sockaddr.ipv4, + sizeof(pj_sockaddr_in)); + pj_memcpy(&sess->result[test_id].ca, &ca->sockaddr.ipv4, + sizeof(pj_sockaddr_in)); + } + + /* Send Test 1B only when Test 2 completes. Must not send Test 1B + * before Test 2 completes to avoid creating mapping on the NAT. + */ + if (!sess->result[ST_TEST_1B].executed && + sess->result[ST_TEST_2].complete && + sess->result[ST_TEST_2].status != PJ_SUCCESS && + sess->result[ST_TEST_1].complete && + sess->result[ST_TEST_1].status == PJ_SUCCESS) + { + cmp = pj_memcmp(&sess->local_addr, &sess->result[ST_TEST_1].ma, + sizeof(pj_sockaddr_in)); + if (cmp != 0) + send_test(sess, ST_TEST_1B, &sess->result[ST_TEST_1].ca, 0); + } + + if (test_completed(sess)<3 || test_completed(sess)!=test_executed(sess)) + goto on_return; + + /* Handle the test result according to RFC 3489 page 22: + + + +--------+ + | Test | + | 1 | + +--------+ + | + | + V + /\ /\ + N / \ Y / \ Y +--------+ + UDP <-------/Resp\--------->/ IP \------------->| Test | + Blocked \ ? / \Same/ | 2 | + \ / \? / +--------+ + \/ \/ | + | N | + | V + V /\ + +--------+ Sym. N / \ + | Test | UDP <---/Resp\ + | 2 | Firewall \ ? / + +--------+ \ / + | \/ + V |Y + /\ /\ | + Symmetric N / \ +--------+ N / \ V + NAT <--- / IP \<-----| Test |<--- /Resp\ Open + \Same/ | 1B | \ ? / Internet + \? / +--------+ \ / + \/ \/ + | |Y + | | + | V + | Full + | Cone + V /\ + +--------+ / \ Y + | Test |------>/Resp\---->Restricted + | 3 | \ ? / + +--------+ \ / + \/ + |N + | Port + +------>Restricted + + Figure 2: Flow for type discovery process + */ + + switch (sess->result[ST_TEST_1].status) { + case PJNATH_ESTUNTIMEDOUT: + /* + * Test 1 has timed-out. Conclude with NAT_TYPE_BLOCKED. + */ + end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_BLOCKED); + break; + case PJ_SUCCESS: + /* + * Test 1 is successful. Further tests are needed to detect + * NAT type. Compare the MAPPED-ADDRESS with the local address. + */ + cmp = pj_memcmp(&sess->local_addr, &sess->result[ST_TEST_1].ma, + sizeof(pj_sockaddr_in)); + if (cmp==0) { + /* + * MAPPED-ADDRESS and local address is equal. Need one more + * test to determine NAT type. + */ + switch (sess->result[ST_TEST_2].status) { + case PJ_SUCCESS: + /* + * Test 2 is also successful. We're in the open. + */ + end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_OPEN); + break; + case PJNATH_ESTUNTIMEDOUT: + /* + * Test 2 has timed out. We're behind somekind of UDP + * firewall. + */ + end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_SYMMETRIC_UDP); + break; + default: + /* + * We've got other error with Test 2. + */ + end_session(sess, sess->result[ST_TEST_2].status, + PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + break; + } + } else { + /* + * MAPPED-ADDRESS is different than local address. + * We're behind NAT. + */ + switch (sess->result[ST_TEST_2].status) { + case PJ_SUCCESS: + /* + * Test 2 is successful. We're behind a full-cone NAT. + */ + end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_FULL_CONE); + break; + case PJNATH_ESTUNTIMEDOUT: + /* + * Test 2 has timed-out Check result of test 1B.. + */ + switch (sess->result[ST_TEST_1B].status) { + case PJ_SUCCESS: + /* + * Compare the MAPPED-ADDRESS of test 1B with the + * MAPPED-ADDRESS returned in test 1.. + */ + cmp = pj_memcmp(&sess->result[ST_TEST_1].ma, + &sess->result[ST_TEST_1B].ma, + sizeof(pj_sockaddr_in)); + if (cmp != 0) { + /* + * MAPPED-ADDRESS is different, we're behind a + * symmetric NAT. + */ + end_session(sess, PJ_SUCCESS, + PJ_STUN_NAT_TYPE_SYMMETRIC); + } else { + /* + * MAPPED-ADDRESS is equal. We're behind a restricted + * or port-restricted NAT, depending on the result of + * test 3. + */ + switch (sess->result[ST_TEST_3].status) { + case PJ_SUCCESS: + /* + * Test 3 is successful, we're behind a restricted + * NAT. + */ + end_session(sess, PJ_SUCCESS, + PJ_STUN_NAT_TYPE_RESTRICTED); + break; + case PJNATH_ESTUNTIMEDOUT: + /* + * Test 3 failed, we're behind a port restricted + * NAT. + */ + end_session(sess, PJ_SUCCESS, + PJ_STUN_NAT_TYPE_PORT_RESTRICTED); + break; + default: + /* + * Got other error with test 3. + */ + end_session(sess, sess->result[ST_TEST_3].status, + PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + break; + } + } + break; + case PJNATH_ESTUNTIMEDOUT: + /* + * Strangely test 1B has failed. Maybe connectivity was + * lost? Or perhaps port 3489 (the usual port number in + * CHANGED-ADDRESS) is blocked? + */ + switch (sess->result[ST_TEST_3].status) { + case PJ_SUCCESS: + /* Although test 1B failed, test 3 was successful. + * It could be that port 3489 is blocked, while the + * NAT itself looks to be a Restricted one. + */ + end_session(sess, PJ_SUCCESS, + PJ_STUN_NAT_TYPE_RESTRICTED); + break; + default: + /* Can't distinguish between Symmetric and Port + * Restricted, so set the type to Unknown + */ + end_session(sess, PJ_SUCCESS, + PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + break; + } + break; + default: + /* + * Got other error with test 1B. + */ + end_session(sess, sess->result[ST_TEST_1B].status, + PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + break; + } + break; + default: + /* + * We've got other error with Test 2. + */ + end_session(sess, sess->result[ST_TEST_2].status, + PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + break; + } + } + break; + default: + /* + * We've got other error with Test 1. + */ + end_session(sess, sess->result[ST_TEST_1].status, + PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + break; + } + +on_return: + pj_mutex_unlock(sess->mutex); +} + + +/* Perform test */ +static pj_status_t send_test(nat_detect_session *sess, + enum test_type test_id, + const pj_sockaddr_in *alt_addr, + pj_uint32_t change_flag) +{ + pj_uint32_t magic, tsx_id[3]; + pj_status_t status; + + sess->result[test_id].executed = PJ_TRUE; + + /* Randomize tsx id */ + do { + magic = pj_rand(); + } while (magic == PJ_STUN_MAGIC); + + tsx_id[0] = pj_rand(); + tsx_id[1] = pj_rand(); + tsx_id[2] = test_id; + + /* Create BIND request */ + status = pj_stun_session_create_req(sess->stun_sess, + PJ_STUN_BINDING_REQUEST, magic, + (pj_uint8_t*)tsx_id, + &sess->result[test_id].tdata); + if (status != PJ_SUCCESS) + goto on_error; + + /* Add CHANGE-REQUEST attribute */ + status = pj_stun_msg_add_uint_attr(sess->pool, + sess->result[test_id].tdata->msg, + PJ_STUN_ATTR_CHANGE_REQUEST, + change_flag); + if (status != PJ_SUCCESS) + goto on_error; + + /* Configure alternate address */ + if (alt_addr) + sess->cur_server = (pj_sockaddr_in*) alt_addr; + else + sess->cur_server = &sess->server; + + PJ_LOG(5,(sess->pool->obj_name, + "Performing %s to %s:%d", + test_names[test_id], + pj_inet_ntoa(sess->cur_server->sin_addr), + pj_ntohs(sess->cur_server->sin_port))); + + /* Send the request */ + status = pj_stun_session_send_msg(sess->stun_sess, NULL, PJ_TRUE, + PJ_TRUE, sess->cur_server, + sizeof(pj_sockaddr_in), + sess->result[test_id].tdata); + if (status != PJ_SUCCESS) + goto on_error; + + return PJ_SUCCESS; + +on_error: + sess->result[test_id].complete = PJ_TRUE; + sess->result[test_id].status = status; + + return status; +} + + +/* Timer callback */ +static void on_sess_timer(pj_timer_heap_t *th, + pj_timer_entry *te) +{ + nat_detect_session *sess; + + sess = (nat_detect_session*) te->user_data; + + if (te->id == TIMER_DESTROY) { + pj_mutex_lock(sess->mutex); + pj_ioqueue_unregister(sess->key); + sess->key = NULL; + sess->sock = PJ_INVALID_SOCKET; + te->id = 0; + pj_mutex_unlock(sess->mutex); + + sess_destroy(sess); + + } else if (te->id == TIMER_TEST) { + + pj_bool_t next_timer; + + pj_mutex_lock(sess->mutex); + + next_timer = PJ_FALSE; + + if (sess->timer_executed == 0) { + send_test(sess, ST_TEST_1, NULL, 0); + next_timer = PJ_TRUE; + } else if (sess->timer_executed == 1) { + send_test(sess, ST_TEST_2, NULL, CHANGE_IP_PORT_FLAG); + next_timer = PJ_TRUE; + } else if (sess->timer_executed == 2) { + send_test(sess, ST_TEST_3, NULL, CHANGE_PORT_FLAG); + } else { + pj_assert(!"Shouldn't have timer at this state"); + } + + ++sess->timer_executed; + + if (next_timer) { + pj_time_val delay = {0, TEST_INTERVAL}; + pj_timer_heap_schedule(th, te, &delay); + } else { + te->id = 0; + } + + pj_mutex_unlock(sess->mutex); + + } else { + pj_assert(!"Invalid timer ID"); + } +} + |