static void reheap_down(pj_timer_heap_t *ht, pj_timer_entry *moved_node, size_t slot, size_t child) { PJ_CHECK_STACK(); // Restore the heap property after a deletion. while (child < ht->cur_size) { // Choose the smaller of the two children. if (child + 1 < ht->cur_size && PJ_TIME_VAL_LT(ht->heap[child + 1]->_timer_value, ht->heap[child]->_timer_value)) child++; // Perform a <copy> if the child has a larger timeout value than // the <moved_node>. if (PJ_TIME_VAL_LT(ht->heap[child]->_timer_value, moved_node->_timer_value)) { copy_node( ht, slot, ht->heap[child]); slot = child; child = HEAP_LEFT(child); } else // We've found our location in the heap. break; } copy_node( ht, slot, moved_node); }
static void reheap_up( pj_timer_heap_t *ht, pj_timer_entry *moved_node, size_t slot, size_t parent) { // Restore the heap property after an insertion. while (slot > 0) { // If the parent node is greater than the <moved_node> we need // to copy it down. if (PJ_TIME_VAL_LT(moved_node->_timer_value, ht->heap[parent]->_timer_value)) { copy_node(ht, slot, ht->heap[parent]); slot = parent; parent = HEAP_PARENT(slot); } else break; } // Insert the new node into its proper resting place in the heap and // update the corresponding slot in the parallel <timer_ids> array. copy_node(ht, slot, moved_node); }
bool operator < (const Pj_Time_Val &rhs) const { return PJ_TIME_VAL_LT((*this), rhs); }
/* * Get NTP time. */ PJ_DEF(pj_status_t) pjmedia_rtcp_get_ntp_time(const pjmedia_rtcp_session *sess, pjmedia_rtcp_ntp_rec *ntp) { /* Seconds between 1900-01-01 to 1970-01-01 */ #define JAN_1970 (2208988800UL) pj_timestamp ts; pj_status_t status; status = pj_get_timestamp(&ts); /* Fill up the high 32bit part */ ntp->hi = (pj_uint32_t)((ts.u64 - sess->ts_base.u64) / sess->ts_freq.u64) + sess->tv_base.sec + JAN_1970; /* Calculate seconds fractions */ ts.u64 = (ts.u64 - sess->ts_base.u64) % sess->ts_freq.u64; pj_assert(ts.u64 < sess->ts_freq.u64); ts.u64 = (ts.u64 << 32) / sess->ts_freq.u64; /* Fill up the low 32bit part */ ntp->lo = ts.u32.lo; #if (defined(PJ_WIN32) && PJ_WIN32!=0) || \ (defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE!=0) /* On Win32, since we use QueryPerformanceCounter() as the backend * timestamp API, we need to protect against this bug: * Performance counter value may unexpectedly leap forward * http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q274323 */ { /* * Compare elapsed time reported by timestamp with actual elapsed * time. If the difference is too excessive, then we use system * time instead. */ /* MIN_DIFF needs to be large enough so that "normal" diff caused * by system activity or context switch doesn't trigger the time * correction. */ enum { MIN_DIFF = 400 }; pj_time_val ts_time, elapsed, diff; pj_gettimeofday(&elapsed); ts_time.sec = ntp->hi - sess->tv_base.sec - JAN_1970; ts_time.msec = (long)(ntp->lo * 1000.0 / 0xFFFFFFFF); PJ_TIME_VAL_SUB(elapsed, sess->tv_base); if (PJ_TIME_VAL_LT(ts_time, elapsed)) { diff = elapsed; PJ_TIME_VAL_SUB(diff, ts_time); } else { diff = ts_time; PJ_TIME_VAL_SUB(diff, elapsed); } if (PJ_TIME_VAL_MSEC(diff) >= MIN_DIFF) { TRACE_((sess->name, "RTCP NTP timestamp corrected by %d ms", PJ_TIME_VAL_MSEC(diff))); ntp->hi = elapsed.sec + sess->tv_base.sec + JAN_1970; ntp->lo = (elapsed.msec * 65536 / 1000) << 16; } } #endif return status; }
int timestamp_test(void) { enum { CONSECUTIVE_LOOP = 100 }; volatile unsigned i; pj_timestamp freq, t1, t2; pj_time_val tv1, tv2; unsigned elapsed; pj_status_t rc; PJ_LOG(3,(THIS_FILE, "...Testing timestamp (high res time)")); /* Get and display timestamp frequency. */ if ((rc=pj_get_timestamp_freq(&freq)) != PJ_SUCCESS) { app_perror("...ERROR: get timestamp freq", rc); return -1000; } PJ_LOG(3,(THIS_FILE, "....frequency: hiword=%lu loword=%lu", freq.u32.hi, freq.u32.lo)); PJ_LOG(3,(THIS_FILE, "...checking if time can run backwards (pls wait)..")); /* * Check if consecutive readings should yield timestamp value * that is bigger than previous value. * First we get the first timestamp. */ rc = pj_get_timestamp(&t1); if (rc != PJ_SUCCESS) { app_perror("...ERROR: pj_get_timestamp", rc); return -1001; } rc = pj_gettimeofday(&tv1); if (rc != PJ_SUCCESS) { app_perror("...ERROR: pj_gettimeofday", rc); return -1002; } for (i=0; i<CONSECUTIVE_LOOP; ++i) { pj_thread_sleep(pj_rand() % 100); rc = pj_get_timestamp(&t2); if (rc != PJ_SUCCESS) { app_perror("...ERROR: pj_get_timestamp", rc); return -1003; } rc = pj_gettimeofday(&tv2); if (rc != PJ_SUCCESS) { app_perror("...ERROR: pj_gettimeofday", rc); return -1004; } /* compare t2 with t1, expecting t2 >= t1. */ if (t2.u32.hi < t1.u32.hi || (t2.u32.hi == t1.u32.hi && t2.u32.lo < t1.u32.lo)) { PJ_LOG(3,(THIS_FILE, "...ERROR: timestamp run backwards!")); return -1005; } /* compare tv2 with tv1, expecting tv2 >= tv1. */ if (PJ_TIME_VAL_LT(tv2, tv1)) { PJ_LOG(3,(THIS_FILE, "...ERROR: time run backwards!")); return -1006; } } /* * Simple test to time some loop. */ PJ_LOG(3,(THIS_FILE, "....testing simple 1000000 loop")); /* Mark start time. */ if ((rc=pj_get_timestamp(&t1)) != PJ_SUCCESS) { app_perror("....error: cat't get timestamp", rc); return -1010; } /* Loop.. */ for (i=0; i<1000000; ++i) { /* Try to do something so that smart compilers wont * remove this silly loop. */ null_func(); } pj_thread_sleep(0); /* Mark end time. */ pj_get_timestamp(&t2); /* Get elapsed time in usec. */ elapsed = pj_elapsed_usec(&t1, &t2); PJ_LOG(3,(THIS_FILE, "....elapsed: %u usec", (unsigned)elapsed)); /* See if elapsed time is "reasonable". * This should be good even on 50Mhz embedded powerpc. */ if (elapsed < 1 || elapsed > 1000000) { PJ_LOG(3,(THIS_FILE, "....error: elapsed time outside window (%u, " "t1.u32.hi=%u, t1.u32.lo=%u, " "t2.u32.hi=%u, t2.u32.lo=%u)", elapsed, t1.u32.hi, t1.u32.lo, t2.u32.hi, t2.u32.lo)); return -1030; } /* Testing time/timestamp accuracy */ rc = timestamp_accuracy(); if (rc != 0) return rc; return 0; }
/* * Keep-alive test. */ static int keep_alive_test(pj_stun_config *cfg) { struct stun_srv *srv; struct stun_client *client; pj_sockaddr_in mapped_addr; pj_stun_sock_info info; pj_str_t srv_addr; pj_time_val timeout, t; int i, ret = 0; pj_status_t status; PJ_LOG(3,(THIS_FILE, " normal operation")); status = create_client(cfg, &client, PJ_TRUE); if (status != PJ_SUCCESS) return -310; status = create_server(client->pool, cfg->ioqueue, RESPOND_STUN|WITH_XOR_MAPPED, &srv); if (status != PJ_SUCCESS) { destroy_client(client); return -320; } /* * Part 1: initial Binding resolution. */ PJ_LOG(3,(THIS_FILE, " initial Binding request")); srv_addr = pj_str("127.0.0.1"); status = pj_stun_sock_start(client->sock, &srv_addr, pj_ntohs(srv->addr.ipv4.sin_port), NULL); if (status != PJ_SUCCESS) { destroy_server(srv); destroy_client(client); return -330; } /* Wait until on_status() callback is called with success status */ pj_gettimeofday(&timeout); timeout.sec += 60; do { handle_events(cfg, 100); pj_gettimeofday(&t); } while (client->on_status_cnt==0 && PJ_TIME_VAL_LT(t, timeout)); /* Check that callback with correct operation is called */ if (client->last_op != PJ_STUN_SOCK_BINDING_OP) { PJ_LOG(3,(THIS_FILE, " error: expecting Binding operation status")); ret = -340; goto on_return; } if (client->last_status != PJ_SUCCESS) { PJ_LOG(3,(THIS_FILE, " error: expecting PJ_SUCCESS status")); ret = -350; goto on_return; } /* Check that client doesn't receive anything */ if (client->on_rx_data_cnt != 0) { PJ_LOG(3,(THIS_FILE, " error: client shouldn't have received anything")); ret = -370; goto on_return; } /* Get info */ pj_bzero(&info, sizeof(info)); pj_stun_sock_get_info(client->sock, &info); /* Check that we have server address */ if (!pj_sockaddr_has_addr(&info.srv_addr)) { PJ_LOG(3,(THIS_FILE, " error: missing server address")); ret = -380; goto on_return; } /* .. and bound address port must not be zero */ if (pj_sockaddr_get_port(&info.bound_addr)==0) { PJ_LOG(3,(THIS_FILE, " error: bound address is zero")); ret = -381; goto on_return; } /* .. and mapped address */ if (!pj_sockaddr_has_addr(&info.mapped_addr)) { PJ_LOG(3,(THIS_FILE, " error: missing mapped address")); ret = -382; goto on_return; } /* verify the mapped address */ pj_sockaddr_in_init(&mapped_addr, &srv->ip_to_send, srv->port_to_send); if (pj_sockaddr_cmp(&info.mapped_addr, &mapped_addr) != 0) { PJ_LOG(3,(THIS_FILE, " error: mapped address mismatched")); ret = -383; goto on_return; } /* .. and at least one alias */ if (info.alias_cnt == 0) { PJ_LOG(3,(THIS_FILE, " error: must have at least one alias")); ret = -384; goto on_return; } if (!pj_sockaddr_has_addr(&info.aliases[0])) { PJ_LOG(3,(THIS_FILE, " error: missing alias")); ret = -386; goto on_return; } /* * Part 2: sending and receiving data */ PJ_LOG(3,(THIS_FILE, " sending/receiving data")); /* Change server operation mode to echo back data */ srv->flag = ECHO; /* Reset server */ srv->rx_cnt = 0; /* Client sending data to echo server */ { char txt[100]; PJ_LOG(3,(THIS_FILE, " sending to %s", pj_sockaddr_print(&info.srv_addr, txt, sizeof(txt), 3))); } status = pj_stun_sock_sendto(client->sock, NULL, &ret, sizeof(ret), 0, &info.srv_addr, pj_sockaddr_get_len(&info.srv_addr)); if (status != PJ_SUCCESS && status != PJ_EPENDING) { app_perror(" error: server sending data", status); ret = -390; goto on_return; } /* Wait for a short period until client receives data. We can't wait for * too long otherwise the keep-alive will kick in. */ pj_gettimeofday(&timeout); timeout.sec += 1; do { handle_events(cfg, 100); pj_gettimeofday(&t); } while (client->on_rx_data_cnt==0 && PJ_TIME_VAL_LT(t, timeout)); /* Check that data is received in server */ if (srv->rx_cnt == 0) { PJ_LOG(3,(THIS_FILE, " error: server didn't receive data")); ret = -395; goto on_return; } /* Check that status is still OK */ if (client->last_status != PJ_SUCCESS) { app_perror(" error: client has failed", client->last_status); ret = -400; goto on_return; } /* Check that data has been received */ if (client->on_rx_data_cnt == 0) { PJ_LOG(3,(THIS_FILE, " error: client doesn't receive data")); ret = -410; goto on_return; } /* * Part 3: Successful keep-alive, */ PJ_LOG(3,(THIS_FILE, " successful keep-alive scenario")); /* Change server operation mode to normal mode */ srv->flag = RESPOND_STUN | WITH_XOR_MAPPED; /* Reset server */ srv->rx_cnt = 0; /* Reset client */ client->on_status_cnt = 0; client->last_status = PJ_SUCCESS; client->on_rx_data_cnt = 0; /* Wait for keep-alive duration to see if client actually sends the * keep-alive. */ pj_gettimeofday(&timeout); timeout.sec += (PJ_STUN_KEEP_ALIVE_SEC + 1); do { handle_events(cfg, 100); pj_gettimeofday(&t); } while (PJ_TIME_VAL_LT(t, timeout)); /* Check that server receives some packets */ if (srv->rx_cnt == 0) { PJ_LOG(3, (THIS_FILE, " error: no keep-alive was received")); ret = -420; goto on_return; } /* Check that client status is still okay and on_status() callback is NOT * called */ /* No longer valid due to this ticket: * http://trac.pjsip.org/repos/ticket/742 if (client->on_status_cnt != 0) { PJ_LOG(3, (THIS_FILE, " error: on_status() must not be called on successful" "keep-alive when mapped-address does not change")); ret = -430; goto on_return; } */ /* Check that client doesn't receive anything */ if (client->on_rx_data_cnt != 0) { PJ_LOG(3,(THIS_FILE, " error: client shouldn't have received anything")); ret = -440; goto on_return; } /* * Part 4: Successful keep-alive with IP address change */ PJ_LOG(3,(THIS_FILE, " mapped IP address change")); /* Change server operation mode to normal mode */ srv->flag = RESPOND_STUN | WITH_XOR_MAPPED; /* Change mapped address in the response */ srv->ip_to_send = pj_str("2.2.2.2"); srv->port_to_send++; /* Reset server */ srv->rx_cnt = 0; /* Reset client */ client->on_status_cnt = 0; client->last_status = PJ_SUCCESS; client->on_rx_data_cnt = 0; /* Wait for keep-alive duration to see if client actually sends the * keep-alive. */ pj_gettimeofday(&timeout); timeout.sec += (PJ_STUN_KEEP_ALIVE_SEC + 1); do { handle_events(cfg, 100); pj_gettimeofday(&t); } while (PJ_TIME_VAL_LT(t, timeout)); /* Check that server receives some packets */ if (srv->rx_cnt == 0) { PJ_LOG(3, (THIS_FILE, " error: no keep-alive was received")); ret = -450; goto on_return; } /* Check that on_status() callback is called (because mapped address * has changed) */ if (client->on_status_cnt != 1) { PJ_LOG(3, (THIS_FILE, " error: on_status() was not called")); ret = -460; goto on_return; } /* Check that callback was called with correct operation */ if (client->last_op != PJ_STUN_SOCK_MAPPED_ADDR_CHANGE) { PJ_LOG(3,(THIS_FILE, " error: expecting keep-alive operation status")); ret = -470; goto on_return; } /* Check that last status is still success */ if (client->last_status != PJ_SUCCESS) { PJ_LOG(3, (THIS_FILE, " error: expecting successful status")); ret = -480; goto on_return; } /* Check that client doesn't receive anything */ if (client->on_rx_data_cnt != 0) { PJ_LOG(3,(THIS_FILE, " error: client shouldn't have received anything")); ret = -490; goto on_return; } /* Get info */ pj_bzero(&info, sizeof(info)); pj_stun_sock_get_info(client->sock, &info); /* Check that we have server address */ if (!pj_sockaddr_has_addr(&info.srv_addr)) { PJ_LOG(3,(THIS_FILE, " error: missing server address")); ret = -500; goto on_return; } /* .. and mapped address */ if (!pj_sockaddr_has_addr(&info.mapped_addr)) { PJ_LOG(3,(THIS_FILE, " error: missing mapped address")); ret = -510; goto on_return; } /* verify the mapped address */ pj_sockaddr_in_init(&mapped_addr, &srv->ip_to_send, srv->port_to_send); if (pj_sockaddr_cmp(&info.mapped_addr, &mapped_addr) != 0) { PJ_LOG(3,(THIS_FILE, " error: mapped address mismatched")); ret = -520; goto on_return; } /* .. and at least one alias */ if (info.alias_cnt == 0) { PJ_LOG(3,(THIS_FILE, " error: must have at least one alias")); ret = -530; goto on_return; } if (!pj_sockaddr_has_addr(&info.aliases[0])) { PJ_LOG(3,(THIS_FILE, " error: missing alias")); ret = -540; goto on_return; } /* * Part 5: Failed keep-alive */ PJ_LOG(3,(THIS_FILE, " failed keep-alive scenario")); /* Change server operation mode to respond without attribute */ srv->flag = RESPOND_STUN; /* Reset server */ srv->rx_cnt = 0; /* Reset client */ client->on_status_cnt = 0; client->last_status = PJ_SUCCESS; client->on_rx_data_cnt = 0; /* Wait until on_status() is called with failure. */ pj_gettimeofday(&timeout); timeout.sec += (PJ_STUN_KEEP_ALIVE_SEC + PJ_STUN_TIMEOUT_VALUE + 5); do { handle_events(cfg, 100); pj_gettimeofday(&t); } while (client->on_status_cnt==0 && PJ_TIME_VAL_LT(t, timeout)); /* Check that callback with correct operation is called */ if (client->last_op != PJ_STUN_SOCK_KEEP_ALIVE_OP) { PJ_LOG(3,(THIS_FILE, " error: expecting keep-alive operation status")); ret = -600; goto on_return; } if (client->last_status == PJ_SUCCESS) { PJ_LOG(3,(THIS_FILE, " error: expecting failed keep-alive")); ret = -610; goto on_return; } /* Check that client doesn't receive anything */ if (client->on_rx_data_cnt != 0) { PJ_LOG(3,(THIS_FILE, " error: client shouldn't have received anything")); ret = -620; goto on_return; } on_return: destroy_server(srv); destroy_client(client); for (i=0; i<7; ++i) handle_events(cfg, 50); return ret; }
PJ_DEF(pj_status_t) pjstun_get_mapped_addr( pj_pool_factory *pf, int sock_cnt, pj_sock_t sock[], const pj_str_t *srv1, int port1, const pj_str_t *srv2, int port2, pj_sockaddr_in mapped_addr[]) { unsigned srv_cnt; pj_sockaddr_in srv_addr[2]; int i, j, send_cnt = 0, nfds; pj_pool_t *pool; struct query_rec { struct { pj_uint32_t mapped_addr; pj_uint32_t mapped_port; } srv[2]; } *rec; void *out_msg; pj_size_t out_msg_len; int wait_resp = 0; pj_status_t status; PJ_CHECK_STACK(); TRACE_((THIS_FILE, "Entering pjstun_get_mapped_addr()")); /* Create pool. */ pool = pj_pool_create(pf, "stun%p", 400, 400, NULL); if (!pool) return PJ_ENOMEM; /* Allocate client records */ rec = (struct query_rec*) pj_pool_calloc(pool, sock_cnt, sizeof(*rec)); if (!rec) { status = PJ_ENOMEM; goto on_error; } TRACE_((THIS_FILE, " Memory allocated.")); /* Create the outgoing BIND REQUEST message template */ status = pjstun_create_bind_req( pool, &out_msg, &out_msg_len, pj_rand(), pj_rand()); if (status != PJ_SUCCESS) goto on_error; TRACE_((THIS_FILE, " Binding request created.")); /* Resolve servers. */ status = pj_sockaddr_in_init(&srv_addr[0], srv1, (pj_uint16_t)port1); if (status != PJ_SUCCESS) goto on_error; srv_cnt = 1; if (srv2 && port2) { status = pj_sockaddr_in_init(&srv_addr[1], srv2, (pj_uint16_t)port2); if (status != PJ_SUCCESS) goto on_error; if (srv_addr[1].sin_addr.s_addr != srv_addr[0].sin_addr.s_addr && srv_addr[1].sin_port != srv_addr[0].sin_port) { srv_cnt++; } } TRACE_((THIS_FILE, " Server initialized, using %d server(s)", srv_cnt)); /* Init mapped addresses to zero */ pj_memset(mapped_addr, 0, sock_cnt * sizeof(pj_sockaddr_in)); /* We need these many responses */ wait_resp = sock_cnt * srv_cnt; TRACE_((THIS_FILE, " Done initialization.")); #if defined(PJ_SELECT_NEEDS_NFDS) && PJ_SELECT_NEEDS_NFDS!=0 nfds = -1; for (i=0; i<sock_cnt; ++i) { if (sock[i] > nfds) { nfds = sock[i]; } } #else nfds = FD_SETSIZE-1; #endif /* Main retransmission loop. */ for (send_cnt=0; send_cnt<MAX_REQUEST; ++send_cnt) { pj_time_val next_tx, now; pj_fd_set_t r; int select_rc; PJ_FD_ZERO(&r); /* Send messages to servers that has not given us response. */ for (i=0; i<sock_cnt && status==PJ_SUCCESS; ++i) { for (j=0; j<srv_cnt && status==PJ_SUCCESS; ++j) { pjstun_msg_hdr *msg_hdr = (pjstun_msg_hdr*) out_msg; pj_ssize_t sent_len; if (rec[i].srv[j].mapped_port != 0) continue; /* Modify message so that we can distinguish response. */ msg_hdr->tsx[2] = pj_htonl(i); msg_hdr->tsx[3] = pj_htonl(j); /* Send! */ sent_len = out_msg_len; status = pj_sock_sendto(sock[i], out_msg, &sent_len, 0, (pj_sockaddr_t*)&srv_addr[j], sizeof(pj_sockaddr_in)); } } /* All requests sent. * The loop below will wait for responses until all responses have * been received (i.e. wait_resp==0) or timeout occurs, which then * we'll go to the next retransmission iteration. */ TRACE_((THIS_FILE, " Request(s) sent, counter=%d", send_cnt)); /* Calculate time of next retransmission. */ pj_gettimeofday(&next_tx); next_tx.sec += (stun_timer[send_cnt]/1000); next_tx.msec += (stun_timer[send_cnt]%1000); pj_time_val_normalize(&next_tx); for (pj_gettimeofday(&now), select_rc=1; status==PJ_SUCCESS && select_rc>=1 && wait_resp>0 && PJ_TIME_VAL_LT(now, next_tx); pj_gettimeofday(&now)) { pj_time_val timeout; timeout = next_tx; PJ_TIME_VAL_SUB(timeout, now); for (i=0; i<sock_cnt; ++i) { PJ_FD_SET(sock[i], &r); } select_rc = pj_sock_select(nfds+1, &r, NULL, NULL, &timeout); TRACE_((THIS_FILE, " select() rc=%d", select_rc)); if (select_rc < 1) continue; for (i=0; i<sock_cnt; ++i) { int sock_idx, srv_idx; pj_ssize_t len; pjstun_msg msg; pj_sockaddr_in addr; int addrlen = sizeof(addr); pjstun_mapped_addr_attr *attr; char recv_buf[128]; if (!PJ_FD_ISSET(sock[i], &r)) continue; len = sizeof(recv_buf); status = pj_sock_recvfrom( sock[i], recv_buf, &len, 0, (pj_sockaddr_t*)&addr, &addrlen); if (status != PJ_SUCCESS) { char errmsg[PJ_ERR_MSG_SIZE]; PJ_LOG(4,(THIS_FILE, "recvfrom() error ignored: %s", pj_strerror(status, errmsg,sizeof(errmsg)).ptr)); /* Ignore non-PJ_SUCCESS status. * It possible that other SIP entity is currently * sending SIP request to us, and because SIP message * is larger than STUN, we could get EMSGSIZE when * we call recvfrom(). */ status = PJ_SUCCESS; continue; } status = pjstun_parse_msg(recv_buf, len, &msg); if (status != PJ_SUCCESS) { char errmsg[PJ_ERR_MSG_SIZE]; PJ_LOG(4,(THIS_FILE, "STUN parsing error ignored: %s", pj_strerror(status, errmsg,sizeof(errmsg)).ptr)); /* Also ignore non-successful parsing. This may not * be STUN response at all. See the comment above. */ status = PJ_SUCCESS; continue; } sock_idx = pj_ntohl(msg.hdr->tsx[2]); srv_idx = pj_ntohl(msg.hdr->tsx[3]); if (sock_idx<0 || sock_idx>=sock_cnt || sock_idx!=i || srv_idx<0 || srv_idx>=2) { status = PJLIB_UTIL_ESTUNININDEX; continue; } if (pj_ntohs(msg.hdr->type) != PJSTUN_BINDING_RESPONSE) { status = PJLIB_UTIL_ESTUNNOBINDRES; continue; } if (rec[sock_idx].srv[srv_idx].mapped_port != 0) { /* Already got response */ continue; } /* From this part, we consider the packet as a valid STUN * response for our request. */ --wait_resp; if (pjstun_msg_find_attr(&msg, PJSTUN_ATTR_ERROR_CODE) != NULL) { status = PJLIB_UTIL_ESTUNRECVERRATTR; continue; } attr = (pjstun_mapped_addr_attr*) pjstun_msg_find_attr(&msg, PJSTUN_ATTR_MAPPED_ADDR); if (!attr) { attr = (pjstun_mapped_addr_attr*) pjstun_msg_find_attr(&msg, PJSTUN_ATTR_XOR_MAPPED_ADDR); if (!attr || attr->family != 1) { status = PJLIB_UTIL_ESTUNNOMAP; continue; } } rec[sock_idx].srv[srv_idx].mapped_addr = attr->addr; rec[sock_idx].srv[srv_idx].mapped_port = attr->port; if (pj_ntohs(attr->hdr.type) == PJSTUN_ATTR_XOR_MAPPED_ADDR) { rec[sock_idx].srv[srv_idx].mapped_addr ^= pj_htonl(STUN_MAGIC); rec[sock_idx].srv[srv_idx].mapped_port ^= pj_htons(STUN_MAGIC >> 16); } } } /* The best scenario is if all requests have been replied. * Then we don't need to go to the next retransmission iteration. */ if (wait_resp <= 0) break; }
/* * Timeout test: scenario when no response is received from server */ static int timeout_test(pj_stun_config *cfg, pj_bool_t destroy_on_err) { struct stun_srv *srv; struct stun_client *client; pj_str_t srv_addr; pj_time_val timeout, t; int ret = 0; pj_status_t status; PJ_LOG(3,(THIS_FILE, " timeout test [%d]", destroy_on_err)); status = create_client(cfg, &client, destroy_on_err); if (status != PJ_SUCCESS) return -10; status = create_server(client->pool, cfg->ioqueue, 0, &srv); if (status != PJ_SUCCESS) { destroy_client(client); return -20; } srv_addr = pj_str("127.0.0.1"); status = pj_stun_sock_start(client->sock, &srv_addr, pj_ntohs(srv->addr.ipv4.sin_port), NULL); if (status != PJ_SUCCESS) { destroy_server(srv); destroy_client(client); return -30; } /* Wait until on_status() callback is called with the failure */ pj_gettimeofday(&timeout); timeout.sec += 60; do { handle_events(cfg, 100); pj_gettimeofday(&t); } while (client->on_status_cnt==0 && PJ_TIME_VAL_LT(t, timeout)); /* Check that callback with correct operation is called */ if (client->last_op != PJ_STUN_SOCK_BINDING_OP) { PJ_LOG(3,(THIS_FILE, " error: expecting Binding operation status")); ret = -40; goto on_return; } /* .. and with the correct status */ if (client->last_status != PJNATH_ESTUNTIMEDOUT) { PJ_LOG(3,(THIS_FILE, " error: expecting PJNATH_ESTUNTIMEDOUT")); ret = -50; goto on_return; } /* Check that server received correct retransmissions */ if (srv->rx_cnt != PJ_STUN_MAX_TRANSMIT_COUNT) { PJ_LOG(3,(THIS_FILE, " error: expecting %d retransmissions, got %d", PJ_STUN_MAX_TRANSMIT_COUNT, srv->rx_cnt)); ret = -60; goto on_return; } /* Check that client doesn't receive anything */ if (client->on_rx_data_cnt != 0) { PJ_LOG(3,(THIS_FILE, " error: client shouldn't have received anything")); ret = -70; goto on_return; } on_return: destroy_server(srv); destroy_client(client); return ret; }
/* * Invalid response scenario: when server returns no MAPPED-ADDRESS or * XOR-MAPPED-ADDRESS attribute. */ static int missing_attr_test(pj_stun_config *cfg, pj_bool_t destroy_on_err) { struct stun_srv *srv; struct stun_client *client; pj_str_t srv_addr; pj_time_val timeout, t; int i, ret = 0; pj_status_t status; PJ_LOG(3,(THIS_FILE, " missing attribute test [%d]", destroy_on_err)); status = create_client(cfg, &client, destroy_on_err); if (status != PJ_SUCCESS) return -110; status = create_server(client->pool, cfg->ioqueue, RESPOND_STUN, &srv); if (status != PJ_SUCCESS) { destroy_client(client); return -120; } srv_addr = pj_str("127.0.0.1"); status = pj_stun_sock_start(client->sock, &srv_addr, pj_ntohs(srv->addr.ipv4.sin_port), NULL); if (status != PJ_SUCCESS) { destroy_server(srv); destroy_client(client); return -130; } /* Wait until on_status() callback is called with the failure */ pj_gettimeofday(&timeout); timeout.sec += 60; do { handle_events(cfg, 100); pj_gettimeofday(&t); } while (client->on_status_cnt==0 && PJ_TIME_VAL_LT(t, timeout)); /* Check that callback with correct operation is called */ if (client->last_op != PJ_STUN_SOCK_BINDING_OP) { PJ_LOG(3,(THIS_FILE, " error: expecting Binding operation status")); ret = -140; goto on_return; } if (client->last_status != PJNATH_ESTUNNOMAPPEDADDR) { PJ_LOG(3,(THIS_FILE, " error: expecting PJNATH_ESTUNNOMAPPEDADDR")); ret = -150; goto on_return; } /* Check that client doesn't receive anything */ if (client->on_rx_data_cnt != 0) { PJ_LOG(3,(THIS_FILE, " error: client shouldn't have received anything")); ret = -170; goto on_return; } on_return: destroy_server(srv); destroy_client(client); for (i=0; i<7; ++i) handle_events(cfg, 50); return ret; }
PJ_DECL(pj_status_t) pjstun_get_mapped_addr( pj_pool_factory *pf, int sock_cnt, pj_sock_t sock[], const pj_str_t *srv1, int port1, const pj_str_t *srv2, int port2, pj_sockaddr_in mapped_addr[]) { pj_sockaddr_in srv_addr[2]; int i, j, send_cnt = 0; pj_pool_t *pool; struct { struct { pj_uint32_t mapped_addr; pj_uint32_t mapped_port; } srv[2]; } *rec; void *out_msg; pj_size_t out_msg_len; int wait_resp = 0; pj_status_t status; PJ_CHECK_STACK(); /* Create pool. */ pool = pj_pool_create(pf, "stun%p", 1024, 1024, NULL); if (!pool) return PJ_ENOMEM; /* Allocate client records */ rec = pj_pool_calloc(pool, sock_cnt, sizeof(*rec)); if (!rec) { status = PJ_ENOMEM; goto on_error; } /* Create the outgoing BIND REQUEST message template */ status = pjstun_create_bind_req( pool, &out_msg, &out_msg_len, pj_rand(), pj_rand()); if (status != PJ_SUCCESS) goto on_error; /* Resolve servers. */ status = pj_sockaddr_in_init(&srv_addr[0], srv1, (pj_uint16_t)port1); if (status != PJ_SUCCESS) goto on_error; status = pj_sockaddr_in_init(&srv_addr[1], srv2, (pj_uint16_t)port2); if (status != PJ_SUCCESS) goto on_error; /* Init mapped addresses to zero */ pj_memset(mapped_addr, 0, sock_cnt * sizeof(pj_sockaddr_in)); /* Main retransmission loop. */ for (send_cnt=0; send_cnt<MAX_REQUEST; ++send_cnt) { pj_time_val next_tx, now; pj_fd_set_t r; int select_rc; PJ_FD_ZERO(&r); /* Send messages to servers that has not given us response. */ for (i=0; i<sock_cnt && status==PJ_SUCCESS; ++i) { for (j=0; j<2 && status==PJ_SUCCESS; ++j) { pjstun_msg_hdr *msg_hdr = out_msg; pj_ssize_t sent_len; if (rec[i].srv[j].mapped_port != 0) continue; /* Modify message so that we can distinguish response. */ msg_hdr->tsx[2] = pj_htonl(i); msg_hdr->tsx[3] = pj_htonl(j); /* Send! */ sent_len = out_msg_len; status = pj_sock_sendto(sock[i], out_msg, &sent_len, 0, (pj_sockaddr_t*)&srv_addr[j], sizeof(pj_sockaddr_in)); if (status == PJ_SUCCESS) ++wait_resp; } } /* All requests sent. * The loop below will wait for responses until all responses have * been received (i.e. wait_resp==0) or timeout occurs, which then * we'll go to the next retransmission iteration. */ /* Calculate time of next retransmission. */ pj_gettimeofday(&next_tx); next_tx.sec += (stun_timer[send_cnt]/1000); next_tx.msec += (stun_timer[send_cnt]%1000); pj_time_val_normalize(&next_tx); for (pj_gettimeofday(&now), select_rc=1; status==PJ_SUCCESS && select_rc==1 && wait_resp>0 && PJ_TIME_VAL_LT(now, next_tx); pj_gettimeofday(&now)) { pj_time_val timeout; timeout = next_tx; PJ_TIME_VAL_SUB(timeout, now); for (i=0; i<sock_cnt; ++i) { PJ_FD_SET(sock[i], &r); } select_rc = pj_sock_select(FD_SETSIZE, &r, NULL, NULL, &timeout); if (select_rc < 1) continue; for (i=0; i<sock_cnt; ++i) { int sock_idx, srv_idx; pj_ssize_t len; pjstun_msg msg; pj_sockaddr_in addr; int addrlen = sizeof(addr); pjstun_mapped_addr_attr *attr; char recv_buf[128]; if (!PJ_FD_ISSET(sock[i], &r)) continue; len = sizeof(recv_buf); status = pj_sock_recvfrom( sock[i], recv_buf, &len, 0, (pj_sockaddr_t*)&addr, &addrlen); --wait_resp; if (status != PJ_SUCCESS) continue; status = pjstun_parse_msg(recv_buf, len, &msg); if (status != PJ_SUCCESS) { continue; } sock_idx = pj_ntohl(msg.hdr->tsx[2]); srv_idx = pj_ntohl(msg.hdr->tsx[3]); if (sock_idx<0 || sock_idx>=sock_cnt || srv_idx<0 || srv_idx>=2) { status = PJLIB_UTIL_ESTUNININDEX; continue; } if (pj_ntohs(msg.hdr->type) != PJSTUN_BINDING_RESPONSE) { status = PJLIB_UTIL_ESTUNNOBINDRES; continue; } if (pjstun_msg_find_attr(&msg, PJSTUN_ATTR_ERROR_CODE) != NULL) { status = PJLIB_UTIL_ESTUNRECVERRATTR; continue; } attr = (void*)pjstun_msg_find_attr(&msg, PJSTUN_ATTR_MAPPED_ADDR); if (!attr) { status = PJLIB_UTIL_ESTUNNOMAP; continue; } rec[sock_idx].srv[srv_idx].mapped_addr = attr->addr; rec[sock_idx].srv[srv_idx].mapped_port = attr->port; } } /* The best scenario is if all requests have been replied. * Then we don't need to go to the next retransmission iteration. */ if (wait_resp <= 0) break; } for (i=0; i<sock_cnt && status==PJ_SUCCESS; ++i) { if (rec[i].srv[0].mapped_addr == rec[i].srv[1].mapped_addr && rec[i].srv[0].mapped_port == rec[i].srv[1].mapped_port) { mapped_addr[i].sin_family = PJ_AF_INET; mapped_addr[i].sin_addr.s_addr = rec[i].srv[0].mapped_addr; mapped_addr[i].sin_port = (pj_uint16_t)rec[i].srv[0].mapped_port; if (rec[i].srv[0].mapped_addr == 0 || rec[i].srv[0].mapped_port == 0) { status = PJLIB_UTIL_ESTUNNOTRESPOND; break; } } else { status = PJLIB_UTIL_ESTUNSYMMETRIC; break; } } pj_pool_release(pool); return status; on_error: if (pool) pj_pool_release(pool); return status; }
/* * The generic test framework, used by most of the tests. */ static int perform_tsx_test(int dummy, char *target_uri, char *from_uri, char *branch_param, int test_time, const pjsip_method *method) { pjsip_tx_data *tdata; pjsip_transaction *tsx; pj_str_t target, from, tsx_key; pjsip_via_hdr *via; pj_time_val timeout; pj_status_t status; PJ_LOG(3,(THIS_FILE, " please standby, this will take at most %d seconds..", test_time)); /* Reset test. */ recv_count = 0; test_complete = 0; /* Init headers. */ target = pj_str(target_uri); from = pj_str(from_uri); /* Create request. */ status = pjsip_endpt_create_request( endpt, method, &target, &from, &target, NULL, NULL, -1, NULL, &tdata); if (status != PJ_SUCCESS) { app_perror(" Error: unable to create request", status); return -100; } /* Set the branch param for test 1. */ via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL); via->branch_param = pj_str(branch_param); /* Add additional reference to tdata to prevent transaction from * deleting it. */ pjsip_tx_data_add_ref(tdata); /* Create transaction. */ status = pjsip_tsx_create_uac( &tsx_user, tdata, &tsx); if (status != PJ_SUCCESS) { app_perror(" Error: unable to create UAC transaction", status); pjsip_tx_data_dec_ref(tdata); return -110; } /* Get transaction key. */ pj_strdup(tdata->pool, &tsx_key, &tsx->transaction_key); /* Send the message. */ status = pjsip_tsx_send_msg(tsx, NULL); // Ignore send result. Some tests do deliberately triggers error // when sending message. if (status != PJ_SUCCESS) { // app_perror(" Error: unable to send request", status); pjsip_tx_data_dec_ref(tdata); // return -120; } /* Set test completion time. */ pj_gettimeofday(&timeout); timeout.sec += test_time; /* Wait until test complete. */ while (!test_complete) { pj_time_val now, poll_delay = {0, 10}; pjsip_endpt_handle_events(endpt, &poll_delay); pj_gettimeofday(&now); if (now.sec > timeout.sec) { PJ_LOG(3,(THIS_FILE, " Error: test has timed out")); pjsip_tx_data_dec_ref(tdata); return -130; } } if (test_complete < 0) { tsx = pjsip_tsx_layer_find_tsx(&tsx_key, PJ_TRUE); if (tsx) { pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED); pj_mutex_unlock(tsx->mutex); flush_events(1000); } pjsip_tx_data_dec_ref(tdata); return test_complete; } else { pj_time_val now; /* Allow transaction to destroy itself */ flush_events(500); /* Wait until test completes */ pj_gettimeofday(&now); if (PJ_TIME_VAL_LT(now, timeout)) { pj_time_val interval; interval = timeout; PJ_TIME_VAL_SUB(interval, now); flush_events(PJ_TIME_VAL_MSEC(interval)); } } /* Make sure transaction has been destroyed. */ if (pjsip_tsx_layer_find_tsx(&tsx_key, PJ_FALSE) != NULL) { PJ_LOG(3,(THIS_FILE, " Error: transaction has not been destroyed")); pjsip_tx_data_dec_ref(tdata); return -140; } /* Check tdata reference counter. */ if (pj_atomic_get(tdata->ref_cnt) != 1) { PJ_LOG(3,(THIS_FILE, " Error: tdata reference counter is %d", pj_atomic_get(tdata->ref_cnt))); pjsip_tx_data_dec_ref(tdata); return -150; } /* Destroy txdata */ pjsip_tx_data_dec_ref(tdata); return PJ_SUCCESS; }
/***************************************************************************** ** ** TEST10_BRANCH_ID: test transport failure in TRYING state. ** TEST11_BRANCH_ID: test transport failure in PROCEEDING state. ** TEST12_BRANCH_ID: test transport failure in CONNECTED state. ** TEST13_BRANCH_ID: test transport failure in CONFIRMED state. ** ***************************************************************************** */ static int tsx_transport_failure_test(void) { struct test_desc { int transport_delay; int fail_delay; char *branch_id; char *title; } tests[] = { { 0, 10, TEST10_BRANCH_ID, "test10: failed transport in TRYING state (no delay)" }, { 50, 10, TEST10_BRANCH_ID, "test10: failed transport in TRYING state (50 ms delay)" }, { 0, 1500, TEST11_BRANCH_ID, "test11: failed transport in PROCEEDING state (no delay)" }, { 50, 1500, TEST11_BRANCH_ID, "test11: failed transport in PROCEEDING state (50 ms delay)" }, { 0, 2500, TEST12_BRANCH_ID, "test12: failed transport in COMPLETED state (no delay)" }, { 50, 2500, TEST12_BRANCH_ID, "test12: failed transport in COMPLETED state (50 ms delay)" }, }; int i, status; for (i=0; i<(int)PJ_ARRAY_SIZE(tests); ++i) { pj_time_val fail_time, end_test, now; PJ_LOG(3,(THIS_FILE, " %s", tests[i].title)); pjsip_loop_set_failure(loop, 0, NULL); pjsip_loop_set_delay(loop, tests[i].transport_delay); status = perform_test(TARGET_URI, FROM_URI, tests[i].branch_id, 0, &pjsip_invite_method, 1, 0, 1); if (status && status != TEST_TIMEOUT_ERROR) return status; if (!status) { PJ_LOG(3,(THIS_FILE, " error: expecting timeout")); return -40; } pj_gettimeofday(&fail_time); fail_time.msec += tests[i].fail_delay; pj_time_val_normalize(&fail_time); do { pj_time_val interval = { 0, 1 }; pj_gettimeofday(&now); pjsip_endpt_handle_events(endpt, &interval); } while (PJ_TIME_VAL_LT(now, fail_time)); pjsip_loop_set_failure(loop, 1, NULL); end_test = now; end_test.sec += 5; do { pj_time_val interval = { 0, 1 }; pj_gettimeofday(&now); pjsip_endpt_handle_events(endpt, &interval); } while (!test_complete && PJ_TIME_VAL_LT(now, end_test)); if (test_complete == 0) { PJ_LOG(3,(THIS_FILE, " error: test has timed out")); return -41; } if (test_complete != 1) return test_complete; } return 0; }
/* Client worker thread */ static int client_thread(void *arg) { pj_time_val end_time, last_report, now; unsigned thread_index = (unsigned)(long)arg; unsigned cycle = 0, last_cycle = 0; pj_thread_sleep(100); pj_gettimeofday(&end_time); end_time.sec += app.client.timeout; pj_gettimeofday(&last_report); if (app.client.first_request.sec == 0) { pj_gettimeofday(&app.client.first_request); } /* Submit all jobs */ while (app.client.job_submitted < app.client.job_count && !app.thread_quit){ pj_time_val timeout = { 0, 1 }; unsigned i; int outstanding; pj_status_t status; /* Calculate current outstanding job */ outstanding = app.client.job_submitted - app.client.job_finished; /* Update stats on max outstanding jobs */ if (outstanding > (int)app.client.stat_max_window) app.client.stat_max_window = outstanding; /* Wait if there are more pending jobs than allowed in the * window. But spawn a new job anyway if no events are happening * after we wait for some time. */ for (i=0; outstanding > (int)app.client.job_window && i<1000; ++i) { pj_time_val wait = { 0, 500 }; unsigned count = 0; pjsip_endpt_handle_events2(app.sip_endpt, &wait, &count); outstanding = app.client.job_submitted - app.client.job_finished; if (count == 0) break; ++cycle; } /* Submit one job */ if (app.client.method.id == PJSIP_INVITE_METHOD) { status = make_call(&app.client.dst_uri); } else if (app.client.stateless) { status = submit_stateless_job(); } else { status = submit_job(); } ++app.client.job_submitted; ++cycle; /* Handle event */ pjsip_endpt_handle_events2(app.sip_endpt, &timeout, NULL); /* Check for time out, also print report */ if (cycle - last_cycle >= 500) { pj_gettimeofday(&now); if (PJ_TIME_VAL_GTE(now, end_time)) { break; } last_cycle = cycle; if (thread_index == 0 && now.sec-last_report.sec >= 2) { printf("\r%d jobs started, %d completed... ", app.client.job_submitted, app.client.job_finished); fflush(stdout); last_report = now; } } } if (app.client.requests_sent.sec == 0) { pj_gettimeofday(&app.client.requests_sent); } if (thread_index == 0) { printf("\r%d jobs started, %d completed%s\n", app.client.job_submitted, app.client.job_finished, (app.client.job_submitted!=app.client.job_finished ? ", waiting..." : ".") ); fflush(stdout); } /* Wait until all jobs completes, or timed out */ pj_gettimeofday(&now); while (PJ_TIME_VAL_LT(now, end_time) && app.client.job_finished < app.client.job_count && !app.thread_quit) { pj_time_val timeout = { 0, 1 }; unsigned i; for (i=0; i<1000; ++i) { unsigned count; count = 0; pjsip_endpt_handle_events2(app.sip_endpt, &timeout, &count); if (count == 0) break; } pj_gettimeofday(&now); } /* Wait couple of seconds to let jobs completes (e.g. ACKs to be sent) */ pj_gettimeofday(&now); end_time = now; end_time.sec += 2; while (PJ_TIME_VAL_LT(now, end_time)) { pj_time_val timeout = { 0, 1 }; unsigned i; for (i=0; i<1000; ++i) { unsigned count; count = 0; pjsip_endpt_handle_events2(app.sip_endpt, &timeout, &count); if (count == 0) break; } pj_gettimeofday(&now); } return 0; }
static int sleep_duration_test(void) { enum { MIS = 20}; unsigned duration[] = { 2000, 1000, 500, 200, 100 }; unsigned i; pj_status_t rc; PJ_LOG(3,(THIS_FILE, "..running sleep duration test")); /* Test pj_thread_sleep() and pj_gettimeofday() */ for (i=0; i<PJ_ARRAY_SIZE(duration); ++i) { pj_time_val start, stop; pj_uint32_t msec; /* Mark start of test. */ rc = pj_gettimeofday(&start); if (rc != PJ_SUCCESS) { app_perror("...error: pj_gettimeofday()", rc); return -10; } /* Sleep */ rc = pj_thread_sleep(duration[i]); if (rc != PJ_SUCCESS) { app_perror("...error: pj_thread_sleep()", rc); return -20; } /* Mark end of test. */ rc = pj_gettimeofday(&stop); /* Calculate duration (store in stop). */ PJ_TIME_VAL_SUB(stop, start); /* Convert to msec. */ msec = PJ_TIME_VAL_MSEC(stop); /* Check if it's within range. */ if (msec < duration[i] * (100-MIS)/100 || msec > duration[i] * (100+MIS)/100) { PJ_LOG(3,(THIS_FILE, "...error: slept for %d ms instead of %d ms " "(outside %d%% err window)", msec, duration[i], MIS)); return -30; } } /* Test pj_thread_sleep() and pj_get_timestamp() and friends */ for (i=0; i<PJ_ARRAY_SIZE(duration); ++i) { pj_time_val t1, t2; pj_timestamp start, stop; pj_uint32_t msec; pj_thread_sleep(0); /* Mark start of test. */ rc = pj_get_timestamp(&start); if (rc != PJ_SUCCESS) { app_perror("...error: pj_get_timestamp()", rc); return -60; } /* ..also with gettimeofday() */ pj_gettimeofday(&t1); /* Sleep */ rc = pj_thread_sleep(duration[i]); if (rc != PJ_SUCCESS) { app_perror("...error: pj_thread_sleep()", rc); return -70; } /* Mark end of test. */ pj_get_timestamp(&stop); /* ..also with gettimeofday() */ pj_gettimeofday(&t2); /* Compare t1 and t2. */ if (PJ_TIME_VAL_LT(t2, t1)) { PJ_LOG(3,(THIS_FILE, "...error: t2 is less than t1!!")); return -75; } /* Get elapsed time in msec */ msec = pj_elapsed_msec(&start, &stop); /* Check if it's within range. */ if (msec < duration[i] * (100-MIS)/100 || msec > duration[i] * (100+MIS)/100) { PJ_LOG(3,(THIS_FILE, "...error: slept for %d ms instead of %d ms " "(outside %d%% err window)", msec, duration[i], MIS)); PJ_TIME_VAL_SUB(t2, t1); PJ_LOG(3,(THIS_FILE, "...info: gettimeofday() reported duration is " "%d msec", PJ_TIME_VAL_MSEC(t2))); return -76; } } /* All done. */ return 0; }