/* $Id$ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * 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 #include #include #include #include #include #include #include #include #include #include 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_grp_lock_t *grp_lock; 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 local_addr; pj_ioqueue_key_t *key; pj_sockaddr server; pj_sockaddr *cur_server; pj_sockaddr cur_addr; 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 src_addr; int src_addr_len; struct result { pj_bool_t executed; pj_bool_t complete; pj_status_t status; pj_sockaddr ma; pj_sockaddr 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 *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); static void sess_on_destroy(void *member); /* * 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; iresult); ++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; iresult); ++i) { if (sess->result[i].complete) ++count; } return count; } static pj_status_t get_local_interface(const pj_sockaddr *server, pj_sockaddr *local_addr) { pj_sock_t sock; pj_sockaddr tmp, local; int addr_len; pj_status_t status; status = pj_sock_socket(server->addr.sa_family, pj_SOCK_DGRAM(), 0, &sock); if (status != PJ_SUCCESS) return status; addr_len = pj_sockaddr_get_len(server); pj_sockaddr_init(server->addr.sa_family, &local, NULL, 0); status = pj_sock_bind(sock, &local, addr_len); if (status != PJ_SUCCESS) { pj_sock_close(sock); return status; } status = pj_sock_connect(sock, server, addr_len); if (status != PJ_SUCCESS) { pj_sock_close(sock); return status; } status = pj_sock_getsockname(sock, &tmp, &addr_len); if (status != PJ_SUCCESS) { pj_sock_close(sock); return status; } pj_sockaddr_cp(local_addr, &tmp); 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_sockaddr srv; if (server) pj_sockaddr_cp(&srv, server); return pj_stun_detect_nat_type2(&srv, stun_cfg, user_data, cb); } PJ_DEF(pj_status_t) pj_stun_detect_nat_type2(const pj_sockaddr *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 af, addr_len; char addr[PJ_INET6_ADDRSTRLEN]; 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_grp_lock_create(pool, NULL, &sess->grp_lock); if (status != PJ_SUCCESS) { /* Group lock not created yet, just destroy pool and return */ pj_pool_release(pool); return status; } pj_grp_lock_add_ref(sess->grp_lock); pj_grp_lock_add_handler(sess->grp_lock, pool, sess, &sess_on_destroy); pj_sockaddr_cp(&sess->server, server); /* * 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. */ af = server->addr.sa_family; status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &sess->sock); if (status != PJ_SUCCESS) goto on_error; /* * Bind to any. */ addr_len = pj_sockaddr_get_len(server); pj_sockaddr_init(server->addr.sa_family, &sess->local_addr, NULL, 0); status = pj_sock_bind(sess->sock, &sess->local_addr, addr_len); if (status != PJ_SUCCESS) goto on_error; /* * Get local/bound address. */ 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); if (status != PJ_SUCCESS) goto on_error; PJ_LOG(5,(sess->pool->obj_name, "Local address is %s:%d", pj_sockaddr_print(&sess->local_addr, addr, sizeof(addr), 2), pj_sockaddr_get_port(&sess->local_addr))); PJ_LOG(5,(sess->pool->obj_name, "Server set to %s:%d", pj_sockaddr_print(server, addr, sizeof(addr), 2), pj_sockaddr_get_port(server))); /* * 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_sock2(sess->pool, stun_cfg->ioqueue, sess->sock, sess->grp_lock, 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->grp_lock, &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); sess->stun_sess = NULL; } if (sess->key) { pj_ioqueue_unregister(sess->key); sess->key = NULL; sess->sock = PJ_INVALID_SOCKET; } else if (sess->sock && sess->sock != PJ_INVALID_SOCKET) { pj_sock_close(sess->sock); sess->sock = PJ_INVALID_SOCKET; } if (sess->grp_lock) { pj_grp_lock_dec_ref(sess->grp_lock); } } static void sess_on_destroy(void *member) { nat_detect_session *sess = (nat_detect_session*)member; 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_grp_lock_acquire(sess->grp_lock); /* Ignore packet when STUN session has been destroyed */ if (!sess->stun_sess) goto on_return; 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, (pj_status_t)-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_grp_lock_release(sess->grp_lock); } /* * 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_grp_lock_acquire(sess->grp_lock); /* 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_PERROR(5,(sess->pool->obj_name, status, "Completed %s", test_names[test_id])); sess->result[test_id].complete = PJ_TRUE; sess->result[test_id].status = status; if (status == PJ_SUCCESS) { pj_sockaddr_cp(&sess->result[test_id].ma, &mattr->sockaddr); pj_sockaddr_cp(&sess->result[test_id].ca, &ca->sockaddr); } /* 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_sockaddr_cmp(&sess->local_addr, &sess->result[ST_TEST_1].ma); 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_sockaddr_cmp(&sess->local_addr, &sess->result[ST_TEST_1].ma); 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_sockaddr_cmp(&sess->result[ST_TEST_1].ma, &sess->result[ST_TEST_1B].ma); 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_grp_lock_release(sess->grp_lock); } /* Perform test */ static pj_status_t send_test(nat_detect_session *sess, enum test_type test_id, const pj_sockaddr *alt_addr, pj_uint32_t change_flag) { pj_uint32_t magic, tsx_id[3]; char addr[PJ_INET6_ADDRSTRLEN]; 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, synthesize it if necessary */ if (alt_addr) { status = pj_sockaddr_synthesize(sess->server.addr.sa_family, &sess->cur_addr, alt_addr); if (status != PJ_SUCCESS) goto on_error; sess->cur_server = &sess->cur_addr; } else { sess->cur_server = &sess->server; } PJ_LOG(5,(sess->pool->obj_name, "Performing %s to %s:%d", test_names[test_id], pj_sockaddr_print(sess->cur_server, addr, sizeof(addr), 2), pj_sockaddr_get_port(sess->cur_server))); /* Send the request */ status = pj_stun_session_send_msg(sess->stun_sess, NULL, PJ_TRUE, PJ_TRUE, sess->cur_server, pj_sockaddr_get_len(sess->cur_server), 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_grp_lock_acquire(sess->grp_lock); pj_ioqueue_unregister(sess->key); sess->key = NULL; sess->sock = PJ_INVALID_SOCKET; te->id = 0; pj_grp_lock_release(sess->grp_lock); sess_destroy(sess); } else if (te->id == TIMER_TEST) { pj_bool_t next_timer; pj_grp_lock_acquire(sess->grp_lock); 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_grp_lock_release(sess->grp_lock); } else { pj_assert(!"Invalid timer ID"); } }